tivohmo 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75d19e33d89ef251caa838694011d40532beb39d
4
- data.tar.gz: ba27d1117f29cb07106b90d152452fe93bae97cb
3
+ metadata.gz: 6513addd49cd73d0c775c59dc61f1b50c1efb335
4
+ data.tar.gz: f1e455479caff464c3bfd800ca7debd5aa2ed57c
5
5
  SHA512:
6
- metadata.gz: b0d86c363409dbc0527e802c483cfc439c6d088439a898f32c1288fb56c88fdee8e8a03ce6a51cc8a7efa30ba29e037827ac643e1c04bee08c6ebb5bc53dca78
7
- data.tar.gz: 5644691da75bed304c6e862ab4e7660372f6684663bb5d621272259fdb6cb89f00d51b264680ce4a8aa4b7fbcabd1f54d0ca9312364da1e8728818be00acdc1a
6
+ metadata.gz: 1c53ffcccd988944ebb7e95ce86a722052ec42198ca1b01d41e26ce71f5e0230652b7585244414b8ab8f9575a1fd5d0dc3e25f8fde1466ec12cb6af27f3f8627
7
+ data.tar.gz: c00d7b88d4551f34fcace28ebd0bd9b714dc8f21926ce3581e918965a77a87d67f5a61f4e1d8bde6e045117bba0044500be3736b58490d56a7f98353f23c3cb1
data/CHANGELOG CHANGED
@@ -1,3 +1,14 @@
1
+ 0.2.0 (11/23/2014)
2
+ ------------------
3
+
4
+ add more metadata from plex <d048373> [Matt Conway]
5
+ add version as option and to normal startup messaging Capture stdout/err to logfile when provided <dd4bae7> [Matt Conway]
6
+ force encoding for detail xml in tivo_header handle negative ItemCount for tivo jump to end of list command <8229e22> [Matt Conway]
7
+ more function launchagent script <51e62de> [Matt Conway]
8
+ refactor plex metadata <76825e7> [Matt Conway]
9
+ unescape filename from plex <0502964> [Matt Conway]
10
+ use forked streamio-ffmpeg (due to lack of maintenance) to allow use with ffmpeg 2.4.x <c7e16e1> [Matt Conway]
11
+
1
12
  0.1.4 (10/12/2014)
2
13
  ------------------
3
14
 
@@ -3,7 +3,7 @@
3
3
  <!--
4
4
  Launchd (OS X) configuration for TivoHMO
5
5
  Copy to ~/Library/LaunchAgents and edit as desired
6
- Then to start the server, run: launchd load ~/Library/LaunchAgents/tivohmo.plist
6
+ Then to start the server, run: launchctl load ~/Library/LaunchAgents/tivohmo.plist
7
7
  -->
8
8
 
9
9
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -13,10 +13,14 @@ Then to start the server, run: launchd load ~/Library/LaunchAgents/tivohmo.plist
13
13
  <string>TivoHMO</string>
14
14
  <key>ProgramArguments</key>
15
15
  <array>
16
- <string>tivohmo</string>
17
- <string>/usr/local/pytivo/pyTivo.py</string>
16
+ <string>bash</string>
17
+ <string>-l</string>
18
+ <string>-c</string>
19
+ <string>tivohmo -p 9033 -l ~/Library/Logs/tivohmo.log -a TivoHMO::Adapters::Plex::Application -i localhost -t 'Plex Videos'</string>
18
20
  </array>
19
21
  <key>RunAtLoad</key>
20
22
  <true/>
23
+ <key>KeepAlive</key>
24
+ <true/>
21
25
  </dict>
22
26
  </plist>
@@ -19,6 +19,32 @@ module TivoHMO
19
19
  self.created_at = Time.at(delegate.added_at.to_i)
20
20
  end
21
21
 
22
+ def metadata
23
+ md = super
24
+
25
+ md.original_air_date = Time.parse(delegate.originally_available_at) rescue nil
26
+
27
+ rating_name = delegate.content_rating.upcase
28
+ rating_value = TivoHMO::API::Metadata::TV_RATINGS[rating_name]
29
+ if rating_value
30
+ md.tv_rating = {name: rating_name, value: rating_value}
31
+ end
32
+
33
+ md.is_episode = true
34
+ md.episode_number = "%i%02i" % [delegate.parent_index, delegate.index]
35
+ md.series_title = delegate.grandparent_title
36
+ md.episode_title = "%i - %s" % [md.episode_number, title]
37
+ md.title = "%s - %s" % [delegate.grandparent_title, title]
38
+
39
+ # group tv shows under same name if we can extract a seriesId
40
+ guid = delegate.guid
41
+ if guid =~ /thetvdb:\/\/(\d+)/
42
+ md.series_id = "SH#{$1}"
43
+ end
44
+
45
+ md
46
+ end
47
+
22
48
  end
23
49
 
24
50
  end
@@ -12,6 +12,13 @@ module TivoHMO
12
12
  begin
13
13
  self.description = item.delegate.summary
14
14
  self.duration = (item.delegate.duration.to_i / 1000).to_i
15
+
16
+ # plex 0-10 => tivo 1-7 for value, 0-4 in .5 increments for name
17
+ plex_rating = item.delegate.rating.to_f
18
+ rating_value = (plex_rating / 10 * 6).round
19
+ rating_name = [1, 1.5, 2, 2.5, 3, 3.5, 4][rating_value]
20
+ self.star_rating = {name: rating_name, value: rating_value + 1}
21
+
15
22
  rescue => e
16
23
  logger.error "Failed to read plex metadata: #{e}"
17
24
  end
@@ -19,6 +19,19 @@ module TivoHMO
19
19
  self.created_at = Time.at(delegate.added_at.to_i)
20
20
  end
21
21
 
22
+ def metadata
23
+ md = super
24
+ md.movie_year = Time.parse(delegate.originally_available_at).year rescue nil
25
+
26
+ rating_name = delegate.content_rating.upcase
27
+ rating_value = TivoHMO::API::Metadata::MPAA_RATINGS[rating_name]
28
+ if rating_value
29
+ md.mpaa_rating = {name: rating_name, value: rating_value}
30
+ end
31
+
32
+ md
33
+ end
34
+
22
35
  end
23
36
 
24
37
  end
@@ -9,7 +9,7 @@ module TivoHMO
9
9
 
10
10
  def initialize(item)
11
11
  super(item)
12
- self.source_filename = item.delegate.medias.first.parts.first.file
12
+ self.source_filename = CGI.unescape(item.delegate.medias.first.parts.first.file)
13
13
  end
14
14
 
15
15
  end
@@ -35,9 +35,9 @@ module TivoHMO
35
35
 
36
36
  def transcoder_options(format="video/x-tivo-mpeg")
37
37
  opts = {
38
- video_max_bitrate: 30000,
38
+ video_max_bitrate: 30_000_000,
39
39
  buffer_size: 4096,
40
- audio_bitrate: 448,
40
+ audio_bitrate: 448_000,
41
41
  format: format,
42
42
  custom: []
43
43
  }
@@ -47,6 +47,7 @@ module TivoHMO
47
47
  opts = select_video_codec(opts)
48
48
  opts = select_video_bitrate(opts)
49
49
  opts = select_audio_codec(opts)
50
+ opts = select_audio_bitrate(opts)
50
51
  opts = select_audio_sample_rate(opts)
51
52
  opts = select_container(opts)
52
53
 
@@ -96,6 +97,13 @@ module TivoHMO
96
97
  opts
97
98
  end
98
99
 
100
+ def select_audio_bitrate(opts)
101
+ # transcode assumes unit of Kbit, whilst video_info has unit of bit
102
+ opts[:audio_bitrate] = (opts[:audio_bitrate] / 1000).to_i
103
+
104
+ opts
105
+ end
106
+
99
107
  def select_audio_codec(opts)
100
108
  if video_info[:audio_codec]
101
109
  if AUDIO_CODECS.any? { |ac| video_info[:audio_codec] =~ /#{ac}/ }
@@ -111,18 +119,24 @@ module TivoHMO
111
119
  end
112
120
 
113
121
  def select_video_bitrate(opts)
114
-
115
122
  vbr = video_info[:video_bitrate]
123
+ default_vbr = 16_384_000
116
124
 
117
125
  if vbr && vbr > 0
118
126
  if vbr >= opts[:video_max_bitrate]
119
- opts[:video_bitrate] = (video_info[:video_max_bitrate] * 0.95).to_i
120
- else
127
+ opts[:video_bitrate] = (opts[:video_max_bitrate] * 0.95).to_i
128
+ elsif vbr > default_vbr
121
129
  opts[:video_bitrate] = vbr
130
+ else
131
+ opts[:video_bitrate] = default_vbr
122
132
  end
123
133
  end
124
134
 
125
- opts[:video_bitrate] ||= 16384
135
+ opts[:video_bitrate] ||= default_vbr
136
+
137
+ # transcode assumes unit of Kbit, whilst video_info has unit of bit
138
+ opts[:video_bitrate] = (opts[:video_bitrate] / 1000).to_i
139
+ opts[:video_max_bitrate] = (opts[:video_max_bitrate] / 1000).to_i
126
140
 
127
141
  opts
128
142
  end
@@ -141,7 +155,6 @@ module TivoHMO
141
155
  end
142
156
 
143
157
  def select_video_dimensions(opts)
144
- preserve_aspect = nil
145
158
  video_width = video_info[:width].to_i
146
159
  VIDEO_WIDTHS.each do |w|
147
160
  w = w.to_i
@@ -163,34 +176,37 @@ module TivoHMO
163
176
  end
164
177
  end
165
178
  video_height = VIDEO_HEIGHTS.last.to_i unless video_height
179
+
166
180
  opts[:resolution] = "#{video_width}x#{video_height}"
167
- opts[:preserve_aspect_ratio] = :height unless opts[:preserve_aspect_ratio]
181
+ opts[:preserve_aspect_ratio] ||= :height
168
182
  opts
169
183
  end
170
184
 
171
185
  def select_video_frame_rate(opts)
172
186
 
173
- frame_rate = video_info[:frame_rate].to_f
174
- VIDEO_FRAME_RATES.each do |r|
175
- if frame_rate >= r.to_f
176
- opts[:frame_rate] = r
177
- break
178
- end
187
+ frame_rate = video_info[:frame_rate]
188
+ if frame_rate =~ /\A[0-9\.]+\Z/
189
+ frame_rate = frame_rate.to_f
190
+ elsif frame_rate =~ /\A\((\d+)\/(\d+)\)\Z/
191
+ frame_rate = $1.to_f / $2.to_f
179
192
  end
180
193
 
181
- opts[:frame_rate] ||= 29.97
194
+ VIDEO_FRAME_RATES.each do |r|
195
+ opts[:frame_rate] = r
196
+ break if frame_rate >= r.to_f
197
+ end
182
198
 
183
199
  opts
184
200
  end
185
201
 
186
202
  def run_transcode(output_filename, format)
187
203
 
188
- logger.debug "Movie Info: " +
204
+ logger.info "Movie Info: " +
189
205
  video_info.collect {|k, v| "#{k}=#{v.inspect}"}.join(' ')
190
206
 
191
207
  opts = transcoder_options(format)
192
208
 
193
- logger.debug "Transcoding options: " +
209
+ logger.info "Transcoding options: " +
194
210
  opts.collect {|k, v| "#{k}='#{v}'"}.join(' ')
195
211
 
196
212
 
@@ -6,6 +6,21 @@ module TivoHMO
6
6
  extend ActiveSupport::Concern
7
7
  include GemLogger::LoggerSupport
8
8
 
9
+ MPAA_RATINGS = {
10
+ 'G' => 1, 'PG' => 2, 'PG-13' => 3, 'PG13' => 3, 'R' => 4, 'X' => 5,
11
+ 'NC-17' => 6, 'NC17' => 6, 'NR' => 8, 'UNRATED' => 8, 'G1' => 1,
12
+ 'P2' => 2, 'P3' => 3, 'R4' => 4, 'X5' => 5, 'N6' => 6, 'N8' => 8
13
+ }
14
+
15
+ TV_RATINGS = {
16
+ 'TV-Y7' => 1, 'TV-Y' => 2, 'TV-G' => 3, 'TV-PG' => 4, 'TV-14' => 5,
17
+ 'TV-MA' => 6, 'TV-NR' => 7, 'TVY7' => 1, 'TVY' => 2, 'TVG' => 3,
18
+ 'TVPG' => 4, 'TV14' => 5, 'TVMA' => 6, 'TVNR' => 7, 'Y7' => 1,
19
+ 'Y' => 2, 'G' => 3, 'PG' => 4, '14' => 5, 'MA' => 6, 'NR' => 7,
20
+ 'UNRATED' => 7, 'X1' => 1, 'X2' => 2, 'X3' => 3, 'X4' => 4, 'X5' => 5,
21
+ 'X6' => 6, 'X7' => 7
22
+ }
23
+
9
24
  attr_accessor :item,
10
25
 
11
26
  :title,
@@ -42,6 +42,10 @@ module TivoHMO
42
42
  :flag, "debug output\n",
43
43
  default: false
44
44
 
45
+ option ["-v", "--version"],
46
+ :flag, "print version and exit\n",
47
+ default: false
48
+
45
49
  option ["-r", "--preload"],
46
50
  :flag, "Preloads all lazy container listings\n",
47
51
  default: false
@@ -83,8 +87,15 @@ module TivoHMO
83
87
  "LIMIT:INTERVAL", "configure beacon limit and/or interval\n"
84
88
 
85
89
  def execute
90
+ if version?
91
+ puts "TivoHMO Version #{TivoHMO::VERSION}"
92
+ return
93
+ end
94
+
86
95
  setup_logging
87
96
 
97
+ logger.info "TivoHMO #{TivoHMO::VERSION} starting up"
98
+
88
99
  if configuration
89
100
  config = YAML.load_file(configuration)
90
101
 
@@ -146,12 +157,21 @@ module TivoHMO
146
157
  Logging.logger.root.level = :debug if debug?
147
158
 
148
159
  if logfile.present?
149
- Logging.logger.root.appenders = Logging.appenders.file(
160
+ appender = Logging.appenders.rolling_file(
150
161
  logfile,
162
+ truncate: true,
163
+ age: 'daily',
164
+ keep: 3,
151
165
  layout: Logging.layouts.pattern(
152
166
  pattern: Logging.appenders.stdout.layout.pattern
153
167
  )
154
168
  )
169
+
170
+ # hack to assign stdout/err to logfile if logging to file
171
+ io = appender.instance_variable_get(:@io)
172
+ $stdout = $stderr = io
173
+
174
+ Logging.logger.root.appenders = appender
155
175
  end
156
176
  end
157
177
 
@@ -147,6 +147,8 @@ module TivoHMO
147
147
  end
148
148
 
149
149
  item_details_xml = builder :item_details, layout: true, locals: {item: item}
150
+ item_details_xml.force_encoding('ascii-8bit')
151
+
150
152
  ld = item_details_xml.bytesize
151
153
  chunk = item_details_xml + '\0' * (pad(ld, 4) + 4)
152
154
  lc = chunk.bytesize
@@ -233,6 +235,11 @@ module TivoHMO
233
235
  else
234
236
  logger.warn "Anchor not found: #{anchor_item}"
235
237
  end
238
+ else
239
+ if item_count < 0
240
+ locals[:item_start] = children.size + item_count
241
+ locals[:item_count] = - item_count
242
+ end
236
243
  end
237
244
 
238
245
  if container.root?
@@ -1,3 +1,3 @@
1
1
  module TivoHMO
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -3,7 +3,13 @@ require 'tivohmo/adapters/plex'
3
3
 
4
4
  describe TivoHMO::Adapters::Plex::Episode do
5
5
 
6
- let(:plex_delegate) { plex_stub(::Plex::Episode) }
6
+ let(:plex_delegate) { plex_stub(::Plex::Episode,
7
+ content_rating: 'G',
8
+ index: 2,
9
+ parent_index: 1,
10
+ grandparent_title: 'ShowTitle',
11
+ guid: "com.plexapp.agents.thetvdb://269650/1/2?lang=en",
12
+ originally_available_at: "2014-06-04") }
7
13
 
8
14
  describe "#initialize" do
9
15
 
@@ -19,4 +25,22 @@ describe TivoHMO::Adapters::Plex::Episode do
19
25
 
20
26
  end
21
27
 
28
+ describe "#metadata" do
29
+
30
+ it "should populate metadata" do
31
+ episode = described_class.new(plex_delegate)
32
+ episode.app = TivoHMO::Adapters::Plex::Application.new('localhost')
33
+ md = episode.metadata
34
+ expect(md.original_air_date).to eq(Time.parse(plex_delegate.originally_available_at))
35
+ expect(md.tv_rating).to eq({name: 'G', value: 3})
36
+ expect(md.is_episode).to eq(true)
37
+ expect(md.episode_number).to eq("102")
38
+ expect(md.series_title).to eq("ShowTitle")
39
+ expect(md.episode_title).to eq("102 - Title")
40
+ expect(md.title).to eq("ShowTitle - Title")
41
+ expect(md.series_id).to eq("SH269650")
42
+ end
43
+
44
+ end
45
+
22
46
  end
@@ -5,7 +5,8 @@ describe TivoHMO::Adapters::Plex::Metadata do
5
5
 
6
6
  let(:plex_delegate) { plex_stub(::Plex::Movie,
7
7
  summary: 'Summary',
8
- duration: 10000) }
8
+ duration: 10000,
9
+ rating: 10) }
9
10
 
10
11
  let(:item) { double('item', delegate: plex_delegate)}
11
12
 
@@ -17,6 +18,7 @@ describe TivoHMO::Adapters::Plex::Metadata do
17
18
  expect(md).to be_a TivoHMO::API::Metadata
18
19
  expect(md.duration).to eq(plex_delegate.duration / 1000)
19
20
  expect(md.description).to eq plex_delegate.summary
21
+ expect(md.star_rating).to eq({name: 4, value: 7})
20
22
  end
21
23
 
22
24
  end
@@ -3,7 +3,9 @@ require 'tivohmo/adapters/plex'
3
3
 
4
4
  describe TivoHMO::Adapters::Plex::Movie do
5
5
 
6
- let(:plex_delegate) { plex_stub(::Plex::Movie) }
6
+ let(:plex_delegate) { plex_stub(::Plex::Movie,
7
+ originally_available_at: "2013-01-02",
8
+ content_rating: 'G') }
7
9
 
8
10
  describe "#initialize" do
9
11
 
@@ -19,4 +21,16 @@ describe TivoHMO::Adapters::Plex::Movie do
19
21
 
20
22
  end
21
23
 
24
+ describe "#metadata" do
25
+
26
+ it "should populate metadata" do
27
+ movie = described_class.new(plex_delegate)
28
+ movie.app = TivoHMO::Adapters::Plex::Application.new('localhost')
29
+ md = movie.metadata
30
+ expect(md.movie_year).to eq(2013)
31
+ expect(md.mpaa_rating).to eq({name: 'G', value: 1})
32
+ end
33
+
34
+ end
35
+
22
36
  end
@@ -9,7 +9,7 @@ describe TivoHMO::Adapters::Plex::Transcoder do
9
9
  duration: 10000,
10
10
  medias: [double('media',
11
11
  parts: [double('part',
12
- file: '/foo')])])}
12
+ file: '/foo%20bar')])])}
13
13
 
14
14
  let(:item) { double('item', identifier: plex_delegate.key, delegate: plex_delegate)}
15
15
 
@@ -19,7 +19,7 @@ describe TivoHMO::Adapters::Plex::Transcoder do
19
19
  trans = described_class.new(item)
20
20
  expect(trans).to be_a described_class
21
21
  expect(trans).to be_a TivoHMO::API::Transcoder
22
- expect(trans.source_filename).to eq '/foo'
22
+ expect(trans.source_filename).to eq '/foo bar'
23
23
  end
24
24
 
25
25
  end
@@ -39,6 +39,16 @@ describe TivoHMO::CLI do
39
39
 
40
40
  end
41
41
 
42
+ describe "--version" do
43
+
44
+ it "produces version text under standard width" do
45
+ expect(cli).to receive(:setup_logging).never
46
+ expect($stdout).to receive(:puts).with(/#{TivoHMO::VERSION}/)
47
+ cli.run(["--version"])
48
+ end
49
+
50
+ end
51
+
42
52
  describe "--debug" do
43
53
 
44
54
  it "defaults to info log level" do
@@ -385,6 +385,22 @@ describe TivoHMO::Server do
385
385
  expect(child_titles).to match_array(["i1", "i2", "i3"])
386
386
  end
387
387
 
388
+ it "honors negative ItemCount (jump to last page)" do
389
+ get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&ItemCount=-3"
390
+ expect(last_response.status).to eq(200)
391
+ doc = Nokogiri::XML(last_response.body)
392
+
393
+ expect(doc.at_xpath("/TiVoContainer/ItemStart").content).to eq("17")
394
+ expect(doc.at_xpath("/TiVoContainer/ItemCount").content).to eq("3")
395
+
396
+ expect(doc.at_xpath("/TiVoContainer/Details/TotalItems").content).to eq("20")
397
+
398
+ expect(doc.xpath("/TiVoContainer/Item").size).to eq (3)
399
+
400
+ child_titles = doc.xpath("/TiVoContainer/Item/Details/Title").collect(&:content)
401
+ expect(child_titles).to match_array(["i18", "i19", "i20"])
402
+ end
403
+
388
404
  it "displays page 2" do
389
405
  get "/TiVoConnect?Command=QueryContainer&Container=/a1/c1&ItemStart=7"
390
406
  expect(last_response.status).to eq(200)
@@ -109,6 +109,9 @@ def plex_stub(clazz, method_stubs={})
109
109
  default_stubs = {
110
110
  key: '/some/key',
111
111
  title: 'Title',
112
+ summary: 'Summary',
113
+ duration: 100,
114
+ rating: 1,
112
115
  updated_at: Time.now.to_i,
113
116
  added_at: Time.now.to_i,
114
117
  refresh: nil
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
39
39
 
40
40
  # filesystem adapter dependencies, make optional?
41
41
  spec.add_dependency "listen"
42
- spec.add_dependency "streamio-ffmpeg"
42
+ spec.add_dependency "tivohmo-streamio-ffmpeg"
43
43
 
44
44
  # plex adapter dependencies, make optional?
45
45
  spec.add_dependency "plex-ruby"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tivohmo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Conway
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-12 00:00:00.000000000 Z
11
+ date: 2014-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -249,7 +249,7 @@ dependencies:
249
249
  - !ruby/object:Gem::Version
250
250
  version: '0'
251
251
  - !ruby/object:Gem::Dependency
252
- name: streamio-ffmpeg
252
+ name: tivohmo-streamio-ffmpeg
253
253
  requirement: !ruby/object:Gem::Requirement
254
254
  requirements:
255
255
  - - ">="
@@ -415,3 +415,4 @@ test_files:
415
415
  - spec/cli_spec.rb
416
416
  - spec/server_spec.rb
417
417
  - spec/spec_helper.rb
418
+ has_rdoc: