apple-tv-converter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ v0.0.1
2
+ ------
3
+ * Mac OSX implementation
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Pedro Rodrigues
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ apple-tv-converter
2
+ =================
3
+
4
+ Media converter for AppleTV.
5
+
6
+ ## On Mac OSX
7
+
8
+ - Uses ffmpeg for encoding (http://ffmpeg.org)
9
+ - Uses Subler for adding the subtitles and setting the iTunes metadata tags (http://code.google.com/p/subler/)
10
+ - Uses MKVToolNix for extracting the embedded subtitles from MKV files (http://www.bunkus.org/videotools/mkvtoolnix/)
11
+
12
+ # TODO
13
+
14
+ - Windows implementation
15
+ - Tests!
16
+ - Create a Mac OSX executable for running the script
@@ -0,0 +1,72 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+
3
+ require 'logger'
4
+ require 'stringio'
5
+ require 'shellwords'
6
+ require 'open3'
7
+ require 'streamio-ffmpeg'
8
+ require 'mkv'
9
+ require 'awesome_print'
10
+ require 'fileutils'
11
+ require 'language_list'
12
+
13
+ require 'apple_tv_converter/version'
14
+ require 'apple_tv_converter/io_patch'
15
+ require 'apple_tv_converter/media_converter'
16
+ require 'apple_tv_converter/media'
17
+ require 'apple_tv_converter/media_converter_adapter'
18
+ require 'apple_tv_converter/media_converter_windows_adapter' if RUBY_PLATFORM =~ /(win|w)(32|64)$/
19
+ require 'apple_tv_converter/media_converter_mac_adapter' if RUBY_PLATFORM =~ /(darwin)/
20
+
21
+ module AppleTvConverter
22
+ # AppleTvConverter logs information about its progress when it's transcoding.
23
+ # Jack in your own logger through this method if you wish to.
24
+ #
25
+ # @param [Logger] log your own logger
26
+ # @return [Logger] the logger you set
27
+ def self.logger=(log)
28
+ @logger = log
29
+ end
30
+
31
+ # Get AppleTvConverter logger.
32
+ #
33
+ # @return [Logger]
34
+ def self.logger
35
+ return @logger if @logger
36
+ logger = Logger.new(STDOUT)
37
+ logger.level = Logger::INFO
38
+ @logger = logger
39
+ end
40
+
41
+ # Set the path of the mp4box binary.
42
+ # Can be useful if you need to specify a path such as /usr/local/bin/mp4box
43
+ #
44
+ # @param [String] path to the mp4box binary
45
+ # @return [String] the path you set
46
+ def self.mp4box_binary=(bin)
47
+ @mp4box_binary = bin
48
+ end
49
+
50
+ # Get the path to the mp4box binary, defaulting to 'MP4Box'
51
+ #
52
+ # @return [String] the path to the mp4box binary
53
+ def self.mp4box_binary
54
+ @mp4box_binary.nil? ? 'MP4Box' : @mp4box_binary
55
+ end
56
+
57
+ # Set the path of the atomic parsley binary.
58
+ # Can be useful if you need to specify a path such as /usr/local/bin/atomic_parsley
59
+ #
60
+ # @param [String] path to the atomic parsley binary
61
+ # @return [String] the path you set
62
+ def self.atomic_parsley_binary=(bin)
63
+ @atomic_parsley_binary = bin
64
+ end
65
+
66
+ # Get the path to the atomic_parsley binary, defaulting to 'AtomicParsley'
67
+ #
68
+ # @return [String] the path to the atomic_parsley binary
69
+ def self.atomic_parsley_binary
70
+ @atomic_parsley_binary.nil? ? 'AtomicParsley' : @atomic_parsley_binary
71
+ end
72
+ end
@@ -0,0 +1,59 @@
1
+ if RUBY_VERSION =~ /1\.8/
2
+ # Useful when `timeout.rb`, which, on M.R.I 1.8, relies on green threads, does not work consistently.
3
+ begin
4
+ require 'system_timer'
5
+ AppleTvConverter::Timer = SystemTimer
6
+ rescue LoadError
7
+ require 'timeout'
8
+ AppleTvConverter::Timer = Timeout
9
+ end
10
+ else
11
+ require 'timeout'
12
+ AppleTvConverter::Timer = Timeout
13
+ end
14
+
15
+ require 'win32/process' if RUBY_PLATFORM =~ /(win|w)(32|64)$/
16
+
17
+ #
18
+ # Monkey Patch timeout support into the IO class
19
+ #
20
+ class IO
21
+ def each_with_timeout(pid, seconds, sep_string=$/)
22
+ sleeping_queue = Queue.new
23
+ thread = nil
24
+
25
+ timer_set = lambda do
26
+ thread = new_thread(pid) { AppleTvConverter::Timer.timeout(seconds) { sleeping_queue.pop } }
27
+ end
28
+
29
+ timer_cancel = lambda do
30
+ thread.kill if thread rescue nil
31
+ end
32
+
33
+ timer_set.call
34
+ each(sep_string) do |buffer|
35
+ timer_cancel.call
36
+ yield buffer
37
+ timer_set.call
38
+ end
39
+ ensure
40
+ timer_cancel.call
41
+ end
42
+
43
+ private
44
+ def new_thread(pid, &block)
45
+ current_thread = Thread.current
46
+ Thread.new do
47
+ begin
48
+ block.call
49
+ rescue Exception => e
50
+ current_thread.raise e
51
+ if RUBY_PLATFORM =~ /(win|w)(32|64)$/
52
+ Process.kill(1, pid)
53
+ else
54
+ Process.kill('SIGKILL', pid)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,58 @@
1
+ module AppleTvConverter
2
+ class Media
3
+ attr_accessor :show, :season, :number, :genre
4
+ attr_reader :original_filename, :quality
5
+
6
+ def original_filename=(value)
7
+ @original_filename = value
8
+
9
+ if @original_filename =~ /.*?\.m4v$/
10
+ if File.exists?(@original_filename.gsub(/\.m4v/, '.avi'))
11
+ @original_filename = @original_filename.gsub(/\.m4v/, '.avi')
12
+ elsif File.exists?(@original_filename.gsub(/\.m4v/, '.mkv'))
13
+ @original_filename = @original_filename.gsub(/\.m4v/, '.mkv')
14
+ end
15
+ end
16
+ end
17
+
18
+ def subtitle_filename
19
+ @subtitle_filename ||= self.original_filename.gsub(/\.(mkv|avi|mp4)/, '.srt')
20
+ end
21
+
22
+ def converted_filename
23
+ @converted_filename ||= self.original_filename.gsub(/\.(mkv|avi)/, '.m4v')
24
+ end
25
+
26
+ def converted_filename_with_subtitles
27
+ @converted_filename_with_subtitles ||= self.original_filename.gsub(/\.(mkv|avi|mp4)/, '_subtitled.m4v')
28
+ end
29
+
30
+ def ffmpeg_data
31
+ @ffmpeg_data ||= FFMPEG::Movie.new(original_filename)
32
+ end
33
+
34
+ def mkv_data
35
+ @mkv_data ||= MKV::Movie.new(original_filename)
36
+ end
37
+
38
+ def is_tv_show_episode?
39
+ !season.nil? && !number.nil?
40
+ end
41
+
42
+ def is_movie?
43
+ !is_tv_show_episode?
44
+ end
45
+
46
+ def is_mkv?
47
+ ffmpeg_data.container =~ /matroska/i rescue File.extname(original_filename).downcase == '.mkv'
48
+ end
49
+
50
+ def is_mp4?
51
+ ffmpeg_data.container =~ /mp4/ rescue File.extname(original_filename) =~ /\.(mp4|m4v)$/
52
+ end
53
+
54
+ def is_valid?
55
+ ffmpeg_data.valid?
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,49 @@
1
+ module AppleTvConverter
2
+ class MediaConverter
3
+ @@timeout = 200
4
+
5
+ def is_windows? ; RUBY_PLATFORM =~/.*?mingw.*?/i ; end
6
+ def is_macosx? ; RUBY_PLATFORM =~/.*?darwin.*?/i ; end
7
+
8
+ def initialize
9
+ @adapter = is_windows? ? AppleTvConverter::MediaConverterWindowsAdapter.new : AppleTvConverter::MediaConverterMacAdapter.new
10
+ end
11
+
12
+ def process_media(media)
13
+ AppleTvConverter.logger.debug "**** #{File.basename(media.original_filename)}"
14
+ AppleTvConverter.logger.debug "* Video codec: #{media.ffmpeg_data.video_codec}"
15
+ AppleTvConverter.logger.debug "* Audio codec: #{media.ffmpeg_data.audio_codec}"
16
+ AppleTvConverter.logger.debug "* Container: #{media.ffmpeg_data.container}" rescue nil
17
+
18
+ extract_subtitles(media) if media.is_mkv?
19
+
20
+ if @adapter.transcode(media)
21
+ @adapter.add_subtitles media
22
+ @adapter.tag media
23
+
24
+ # add_to_itunes media
25
+ @adapter.clean_up media
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # HELPERS
32
+ def process_all_medias(medias)
33
+ medias.each_with_index do |media, index|
34
+ puts '*' * 80
35
+ puts "* [#{(index + 1).to_s.rjust(medias.length.to_s.length, ' ')}/#{medias.length}] #{File.basename(media.original_filename)}"
36
+
37
+ process_media media
38
+ end
39
+ end
40
+
41
+ def process_file(file)
42
+ puts '*' * 80
43
+ puts "* #{file}"
44
+
45
+ media = parse_filename(file)
46
+ process_media media
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,87 @@
1
+ module AppleTvConverter
2
+ class MediaConverterAdapter
3
+ def extract_subtitles(media)
4
+ printf "*** Extracting subtitles"
5
+ media.mkv_data.extract_subtitles(File.dirname(media.original_filename)) do |progress|
6
+ printf "\r* Progress: #{progress}%%"
7
+ end
8
+ puts "\r* Progress: [DONE]"
9
+ end
10
+
11
+ def transcode(media)
12
+ if convert?(media)
13
+ puts "*** Encoding"
14
+
15
+ options = {
16
+ :video_codec => convert_video?(media) ? 'mpeg4' : 'copy',
17
+ :audio_codec => convert_audio?(media) ? 'libfaac' : 'copy'
18
+ }
19
+
20
+ options[:custom] = "-qscale 1" if convert_video?(media)
21
+
22
+ transcoded = media.ffmpeg_data.transcode(media.converted_filename, options) do |progress|
23
+ printf %Q[\r* Progress: #{(progress * 100).round(2)}%%]
24
+ end
25
+
26
+ status transcoded.valid?
27
+
28
+ if status
29
+ puts "\r* Encoding: [DONE]#{' ' * 20}"
30
+ else
31
+ puts "\r* Encoding: [ERROR]#{' ' * 20}"
32
+ end
33
+ else
34
+ puts "*** Encoding: [UNNECESSARY]"
35
+ status = true
36
+ end
37
+
38
+ return status
39
+ end
40
+
41
+ def add_subtitles(media)
42
+ raise NotImplementedYetException
43
+ end
44
+
45
+ def tag(media)
46
+ raise NotImplementedYetException
47
+ end
48
+
49
+ def add_to_itunes(media)
50
+ raise NotImplementedYetException
51
+ end
52
+
53
+ def clean_up(media)
54
+ printf "*** Cleaning up"
55
+ begin
56
+ FileUtils.rm(media.original_filename) unless media.original_filename == media.converted_filename
57
+ FileUtils.rm_r list_files(File.join(File.dirname(media.original_filename), '*.srt'))
58
+ puts " [DONE]"
59
+ rescue
60
+ puts " [ERROR]"
61
+ end
62
+ end
63
+
64
+ protected
65
+
66
+ def list_files(ls)
67
+ raise NotImplementedYetException
68
+ end
69
+
70
+ def convert_audio?(media)
71
+ return media.ffmpeg_data.audio_codec !~ /(?:ac3|aac)/i
72
+ end
73
+
74
+ def convert_video?(media)
75
+ return media.ffmpeg_data.video_codec !~ /.*(?:mpeg4).*/i
76
+ end
77
+
78
+ def convert?(media)
79
+ !(media.is_valid? && media.is_mp4?)# && !convert_video?(media) && !convert_audio?(media))
80
+ end
81
+
82
+ def has_subtitles?(media)
83
+ list_files(File.join(File.dirname(media.original_filename), '*.srt')).any?
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,87 @@
1
+ module AppleTvConverter
2
+ class MediaConverterMacAdapter < MediaConverterAdapter
3
+ def add_subtitles(media)
4
+ puts "*** Adding subtitles"
5
+
6
+ printf "* Removing any previous subtitles"
7
+
8
+ command_line = %Q[./SublerCLI -remove -dest "#{media.converted_filename}"]
9
+
10
+ AppleTvConverter.logger.debug "Executing:"
11
+ AppleTvConverter.logger.debug command_line
12
+
13
+ output = Open3.popen3(command_line) { |stdin, stdout, stderr, wait_thr| stdout.read }
14
+
15
+ puts output.strip.empty? ? " [DONE]" : (output.strip == 'Error: (null)' ? " [NONE FOUND]" : " [ERROR]")
16
+
17
+ if has_subtitles?(media)
18
+ list_files(File.join(File.dirname(media.original_filename), '*.srt')).map do |subtitle_filename|
19
+ subtitle_filename =~ /(\w{3})\.srt$/
20
+ language_code = $1 || 'eng'
21
+
22
+ language = ::LanguageList::LanguageInfo.find_by_iso_639_3(language_code)
23
+ language ||= ::LanguageList::LanguageInfo.find_by_iso_639_3('eng')
24
+
25
+
26
+ command_line = "./SublerCLI "
27
+ command_line << %Q[-source "#{subtitle_filename}" ]
28
+ command_line << %Q[-language "#{language.name}" ]
29
+ command_line << %Q[-dest "#{media.converted_filename}"]
30
+
31
+ AppleTvConverter.logger.debug "Executing:"
32
+ AppleTvConverter.logger.debug command_line
33
+
34
+ printf "* Adding #{language.name} subtitles"
35
+ output = Open3.popen3(command_line) { |stdin, stdout, stderr, wait_thr| stdout.read }
36
+
37
+ puts output.strip.empty? ? " [DONE]" : " [ERROR]"
38
+ end
39
+ else
40
+ puts "* No new subtitles found"
41
+ end
42
+ end
43
+
44
+ def tag(media)
45
+ metadata = ''
46
+ metadata << %Q[{Name: #{media.show} S#{media.season.to_s.rjust(2, '0')}E#{media.number.to_s.rjust(2, '0')}}] if media.is_tv_show_episode?
47
+ metadata << %Q[{Name: #{media.show}}] if media.is_movie?
48
+ metadata << %Q[{Genre: #{media.show}}{TV Show: #{media.show}}{TV Season: #{media.season}}{TV Episode #: #{media.number}}] if media.is_tv_show_episode?
49
+ metadata << %Q[{Genre: #{media.quality} Movie}] if media.is_movie?
50
+
51
+ command_line = %Q[./SublerCLI -metadata "#{metadata}" -dest "#{media.converted_filename}"]
52
+
53
+ AppleTvConverter.logger.debug "Executing:"
54
+ AppleTvConverter.logger.debug command_line
55
+
56
+ printf "*** Tagging"
57
+
58
+ output = Open3.popen3(command_line) { |stdin, stdout, stderr, wait_thr| stdout.read }
59
+
60
+ puts output.strip.empty? ? " [DONE]" : " [ERROR]"
61
+
62
+ return output.strip.empty?
63
+ end
64
+
65
+ def add_to_itunes(media)
66
+ printf "*** Adding to iTunes"
67
+
68
+ command_line = [
69
+ "ln -s",
70
+ "#{media.converted_filename}".gsub(/\s/, '\ ').gsub(/\[/, '\[').gsub(/\]/, '\]'),
71
+ "#{File.expand_path(File.join('~', 'Music', 'iTunes', 'iTunes Media', 'Automatically Add to iTunes.localized'))}".gsub(/\s/, '\ ').gsub(/\[/, '\[').gsub(/\]/, '\]')
72
+ ].join(' ')
73
+
74
+ AppleTvConverter.logger.debug "Executing:"
75
+ AppleTvConverter.logger.debug command_line
76
+
77
+ `#{command_line}`
78
+
79
+ puts ' [DONE]'
80
+ return true
81
+ end
82
+
83
+ def list_files(ls)
84
+ `ls -1 #{ls.gsub(/\s/, '\ ').gsub(/\[/, '\[').gsub(/\]/, '\]')} 2>/dev/null`.split("\n")
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,43 @@
1
+ module AppleTvConverter
2
+ class MediaConverterWindowsAdapter < MediaConverterAdapter
3
+ require 'win32ole'
4
+
5
+ def handbrake_location
6
+ return File.expand_path("./HandBrakeCLI#{'_x64' if is_windows_64bit?}.exe")
7
+ end
8
+
9
+ def atomic_parsley_location
10
+ return File.expand_path('./AtomicParsley.exe')
11
+ end
12
+
13
+ def clean_up_command_line(command_line)
14
+ return command_line.gsub(/\//, '\\')
15
+ end
16
+
17
+ def add_to_itunes(episode)
18
+ printf "* Adding to iTunes"
19
+ @itunes ||= WIN32OLE.new("iTunes.Application")
20
+ @itunes.PlayFile(episode.converted_filename)
21
+ @itunes.Stop
22
+
23
+ puts " [DONE]"
24
+ end
25
+
26
+ def line_ending
27
+ end
28
+
29
+ def execute_command(command_line, &block)
30
+ `#{command_line}`
31
+ end
32
+
33
+ private
34
+
35
+ def is_windows_32bit?
36
+ !is_windows_64bit?
37
+ end
38
+
39
+ def is_windows_64bit?
40
+ ENV.has_key?('ProgramFiles(x86)')
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module AppleTvConverter
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apple-tv-converter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pedro Rodrigues
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '2.7'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '2.7'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.9.2
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.2
46
+ - !ruby/object:Gem::Dependency
47
+ name: mkv
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.0.1
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.0.1
62
+ - !ruby/object:Gem::Dependency
63
+ name: streamio-ffmpeg
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 0.9.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 0.9.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: language_list
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.0.3
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.0.3
94
+ description: Converts movies to a format playable on Apple TV. Supporting multiple
95
+ subtitles.
96
+ email:
97
+ - pedro@bbde.org
98
+ executables: []
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - lib/apple_tv_converter/io_patch.rb
103
+ - lib/apple_tv_converter/media.rb
104
+ - lib/apple_tv_converter/media_converter.rb
105
+ - lib/apple_tv_converter/media_converter_adapter.rb
106
+ - lib/apple_tv_converter/media_converter_mac_adapter.rb
107
+ - lib/apple_tv_converter/media_converter_windows_adapter.rb
108
+ - lib/apple_tv_converter/version.rb
109
+ - lib/apple_tv_converter.rb
110
+ - README.md
111
+ - LICENSE
112
+ - CHANGELOG
113
+ homepage: http://github.com/gokuu/apple-tv-converter
114
+ licenses: []
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 1.8.24
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Converts movies to a format playable on Apple TV.
137
+ test_files: []