apple-tv-converter 0.5.9 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README.md +3 -1
- data/gems/streamio-ffmpeg/lib/ffmpeg/movie.rb +2 -2
- data/gems/streamio-ffmpeg/lib/ffmpeg/stream.rb +5 -1
- data/lib/apple_tv_converter/command_line.rb +20 -1
- data/lib/apple_tv_converter/filename_parser.rb +2 -2
- data/lib/apple_tv_converter/media.rb +60 -14
- data/lib/apple_tv_converter/media_converter.rb +3 -1
- data/lib/apple_tv_converter/media_converter_adapter.rb +38 -13
- data/lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb +14 -4
- data/lib/apple_tv_converter/version.rb +1 -1
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
v0.6.0
|
2
|
+
------
|
3
|
+
* Added command line option to allow resizing the converted video
|
4
|
+
* Enchanced the --os option to allow supplying an username:password combination
|
5
|
+
* Other fixes
|
6
|
+
|
1
7
|
v0.5.9
|
2
8
|
------
|
3
9
|
* Fixed #5 Command line options now correctly override the data file (thanks @shir)
|
data/README.md
CHANGED
@@ -19,7 +19,7 @@ Usage: apple-tv-converter [options] [file]
|
|
19
19
|
-l, --languages eng,por,... Only keep audio and subtitles in the specified languages
|
20
20
|
-d, --dir DIRECTORY Process all files in DIRECTORY recursively
|
21
21
|
--itunes Add processed file to iTunes library, if it isn't present yet
|
22
|
-
--os
|
22
|
+
--os [USERNAME:PASSWORD] Download subtitles and infer IMDB ID from opensubtitles.org
|
23
23
|
--plex Rename file(s) to Plex Media Server recommended format
|
24
24
|
|
25
25
|
--no-transcoding Don't transcode video or audio
|
@@ -35,6 +35,8 @@ Advanced options:
|
|
35
35
|
Set the episode number padding length (ie, 3 for 001, 002, etc.)
|
36
36
|
-s, --season NUMBER Set the season number for TV Shows in case folder/file naming scheme doesn't contain right season
|
37
37
|
-e, --episode NUMBER Set the episode number for TV Shows in case folder/file naming scheme doesn't contain right episode number
|
38
|
+
--width NUMBER Resize the video to the specified width. If used with --height, can result in a different aspect ratio
|
39
|
+
--height NUMBER Resize the video to the specified height. If used with --width, can result in a different aspect ratio
|
38
40
|
|
39
41
|
Other options:
|
40
42
|
-f, --ffmpeg LOCATION Set path to ffmpeg binary
|
@@ -49,14 +49,14 @@ module FFMPEG
|
|
49
49
|
@audio_stream = $1
|
50
50
|
|
51
51
|
if video_stream
|
52
|
-
@video_codec, @colorspace, resolution, video_bitrate = video_stream.
|
52
|
+
@video_codec, @colorspace, resolution, video_bitrate = video_stream.scan(/([^,()\[\]]+(?:\s*(?:\[|\().*?(?:\]|\)))*)\s?,?\s?/).flatten
|
53
53
|
@video_bitrate = video_bitrate =~ %r(\A(\d+) kb/s\Z) ? $1.to_i : nil
|
54
54
|
@resolution = resolution.split(" ").first rescue nil # get rid of [PAR 1:1 DAR 16:9]
|
55
55
|
@dar = $1 if video_stream[/DAR (\d+:\d+)/]
|
56
56
|
end
|
57
57
|
|
58
58
|
if audio_stream
|
59
|
-
@audio_codec, audio_sample_rate, @audio_channels, unused, audio_bitrate = audio_stream.
|
59
|
+
@audio_codec, audio_sample_rate, @audio_channels, unused, audio_bitrate = audio_stream.scan(/([^,()\[\]]+(?:\s*(?:\[|\().*?(?:\]|\)))*)\s?,?\s?/).flatten
|
60
60
|
@audio_bitrate = audio_bitrate =~ %r(\A(\d+) kb/s(?: \(default\))?\Z) ? $1.to_i : nil
|
61
61
|
@audio_sample_rate = audio_sample_rate[/\d*/].to_i
|
62
62
|
end
|
@@ -13,8 +13,12 @@ module FFMPEG
|
|
13
13
|
attr_writer :subtitle_codec, :subtitle_format
|
14
14
|
|
15
15
|
def audio_channels=(value)
|
16
|
-
if value.strip =~ /^(\d+)\.(\d+)
|
16
|
+
if value.strip =~ /^(\d+)\.(\d+)(?:\(.*?\))?$/
|
17
17
|
@audio_channels = [$1.to_i + $2.to_i, 6].min
|
18
|
+
elsif value.strip =~ /stereo/i
|
19
|
+
@audio_channels = 2
|
20
|
+
elsif value.strip =~ /mono/i
|
21
|
+
@audio_channels = 1
|
18
22
|
else
|
19
23
|
@audio_channels = value
|
20
24
|
end
|
@@ -50,6 +50,8 @@ module AppleTvConverter
|
|
50
50
|
options.media = []
|
51
51
|
options.season = nil
|
52
52
|
options.episode = nil
|
53
|
+
options.width = -1
|
54
|
+
options.height = -1
|
53
55
|
|
54
56
|
opts = OptionParser.new do |opts|
|
55
57
|
opts.banner = "Usage: apple-tv-converter [options] [file]\n" +
|
@@ -101,8 +103,15 @@ module AppleTvConverter
|
|
101
103
|
options.add_to_itunes = true
|
102
104
|
end
|
103
105
|
|
104
|
-
opts.on('--os', "Download subtitles and infer IMDB ID from opensubtitles.org") do |
|
106
|
+
opts.on('--os [USERNAME:PASSWORD]', "Download subtitles and infer IMDB ID from opensubtitles.org") do |username_password|
|
105
107
|
options.download_subtitles = true
|
108
|
+
if username_password =~ /^(.*?)\:(.*?)/
|
109
|
+
options.download_subtitles_username = $1 if username_password =~ /^(.+?)\:.+$/
|
110
|
+
options.download_subtitles_password = $1 if username_password =~ /^.+?\:(.+)$/
|
111
|
+
end
|
112
|
+
|
113
|
+
options.download_subtitles_username = nil if options.download_subtitles_username == ''
|
114
|
+
options.download_subtitles_password = nil if options.download_subtitles_password == ''
|
106
115
|
end
|
107
116
|
|
108
117
|
opts.on('--plex', 'Rename file(s) to Plex Media Server recommended format') do
|
@@ -155,6 +164,16 @@ module AppleTvConverter
|
|
155
164
|
options.episode = i.to_i
|
156
165
|
end
|
157
166
|
|
167
|
+
opts.on('--width NUMBER', 'Resize the video to the specified width. If used with --height, can result in a different aspect ratio') do |i|
|
168
|
+
options.width = i.to_i
|
169
|
+
options.width -= options.width % 2 # Ensure it's always a even number
|
170
|
+
end
|
171
|
+
|
172
|
+
opts.on('--height NUMBER', 'Resize the video to the specified height. If used with --width, can result in a different aspect ratio') do |i|
|
173
|
+
options.height = i.to_i
|
174
|
+
options.height -= options.height % 2 # Ensure it's always a even number
|
175
|
+
end
|
176
|
+
|
158
177
|
opts.separator ""
|
159
178
|
opts.separator "Other options:"
|
160
179
|
|
@@ -29,8 +29,8 @@ module AppleTvConverter
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def format1_match
|
32
|
-
# /.*?S(\d+)E(\d+)(?:(?:[-E]+(\d+))*).*?/ -> S00E01, S00E01(E02)+, S00E01(-E02)+, S00E01(-02)+
|
33
|
-
@format1_match ||= basename.match(/.*?S(\d+)E(\d+)(?:(?:[-E]+(\d+))*).*?/i)
|
32
|
+
# /.*?S(\d+)E(\d+)(?:(?:[-E]+(\d+))*).*?/ -> S00E01, S00E01(E02)+, S00E01(-E02)+, S00E01(-02)+, S00 E01( E02)+
|
33
|
+
@format1_match ||= basename.match(/.*?S(\d+)\s*E(\d+)(?:(?:[-E]+(\d+)\s*)*).*?/i)
|
34
34
|
end
|
35
35
|
|
36
36
|
def format2_match
|
@@ -14,19 +14,7 @@ module AppleTvConverter
|
|
14
14
|
def original_filename=(value)
|
15
15
|
@original_filename = value
|
16
16
|
|
17
|
-
|
18
|
-
Dir[@original_filename.gsub(File.extname(@original_filename), '*')].each do |file|
|
19
|
-
if @original_filename != file && !(Media.subtitle_extensions + Media.ignored_extensions).include?(file.downcase.gsub(/.*\./, ''))
|
20
|
-
@original_filename = file
|
21
|
-
break
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
if converted_filename == original_filename && needs_transcoding?
|
27
|
-
@converted_filename = original_filename.gsub(File.extname(original_filename), "_2#{File.extname(original_filename)}")
|
28
|
-
@converted_filename_equals_original_filename = true
|
29
|
-
end
|
17
|
+
check_filename_clashing
|
30
18
|
|
31
19
|
load_data_file
|
32
20
|
|
@@ -120,7 +108,21 @@ module AppleTvConverter
|
|
120
108
|
|
121
109
|
def needs_audio_conversion? ; return ffmpeg_data.audio_codec !~ /(?:aac)/i ; end
|
122
110
|
|
123
|
-
def needs_video_conversion?
|
111
|
+
def needs_video_conversion?
|
112
|
+
return [
|
113
|
+
ffmpeg_data.video_codec !~ /(?:.*?h264|^mpeg4).*/i,
|
114
|
+
ffmpeg_data.video_codec =~ /.*(?:xvid|divx).*/i,
|
115
|
+
ffmpeg_data.video_stream =~ /h264.*?yuv420p10le/i,
|
116
|
+
needs_video_resizing?
|
117
|
+
].any?
|
118
|
+
end
|
119
|
+
|
120
|
+
def needs_video_resizing?
|
121
|
+
return [
|
122
|
+
movie_width > 0 && movie_width != ffmpeg_data.width,
|
123
|
+
movie_height > 0 && movie_height != ffmpeg_data.height
|
124
|
+
].any?
|
125
|
+
end
|
124
126
|
|
125
127
|
def needs_subtitles_conversion? ; return ffmpeg_data.subtitle_streams.any? ; end
|
126
128
|
|
@@ -132,6 +134,22 @@ module AppleTvConverter
|
|
132
134
|
|
133
135
|
def movie_hash ; @movie_hash ||= AppleTvConverter::MovieHasher.compute_hash(original_filename) ; end
|
134
136
|
|
137
|
+
def movie_width ; @movie_width || 0 ; end
|
138
|
+
def movie_width=(value)
|
139
|
+
@movie_width = value
|
140
|
+
assert_movie_dimensions!
|
141
|
+
check_filename_clashing
|
142
|
+
@movie_width
|
143
|
+
end
|
144
|
+
|
145
|
+
def movie_height ; @movie_height || 0 ; end
|
146
|
+
def movie_height=(value)
|
147
|
+
@movie_height = value
|
148
|
+
assert_movie_dimensions!
|
149
|
+
check_filename_clashing
|
150
|
+
@movie_height
|
151
|
+
end
|
152
|
+
|
135
153
|
def tvdb_movie_data(key, default = nil) ;
|
136
154
|
return tvdb_movie[:episode][key].gsub(/`/, '') if tvdb_movie && tvdb_movie.has_key?(:episode) && tvdb_movie[:episode].has_key?(key) && !tvdb_movie[:episode][key].blank? rescue default
|
137
155
|
return default
|
@@ -193,5 +211,33 @@ module AppleTvConverter
|
|
193
211
|
ap ['e', e]
|
194
212
|
end
|
195
213
|
end
|
214
|
+
|
215
|
+
def assert_movie_dimensions!
|
216
|
+
if movie_width > 0 && movie_height <= 0
|
217
|
+
@movie_height = ffmpeg_data.height.to_f * (movie_width.to_f / ffmpeg_data.width.to_f)
|
218
|
+
@movie_height -= @movie_height % 2
|
219
|
+
@movie_height = @movie_height.to_i
|
220
|
+
elsif movie_width <= 0 && movie_height > 0
|
221
|
+
@movie_width = ffmpeg_data.width.to_f * (movie_height.to_f / ffmpeg_data.height.to_f)
|
222
|
+
@movie_width -= movie_width % 2
|
223
|
+
@movie_width = @movie_width.to_i
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def check_filename_clashing
|
228
|
+
if @original_filename =~ /.*?\.mp4$/
|
229
|
+
Dir[@original_filename.gsub(File.extname(@original_filename), '*')].each do |file|
|
230
|
+
if @original_filename != file && !(Media.subtitle_extensions + Media.ignored_extensions).include?(file.downcase.gsub(/.*\./, ''))
|
231
|
+
@original_filename = file
|
232
|
+
break
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
if converted_filename == original_filename && needs_transcoding?
|
238
|
+
@converted_filename = original_filename.gsub(File.extname(original_filename), "_2#{File.extname(original_filename)}")
|
239
|
+
@converted_filename_equals_original_filename = true
|
240
|
+
end
|
241
|
+
end
|
196
242
|
end
|
197
243
|
end
|
@@ -68,7 +68,7 @@ module AppleTvConverter
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
if @options.skip_subtitles != true && @options.download_subtitles && media.subtitle_streams.empty? && @adapter.list_files(media.original_filename.gsub(/.{4}$/, '.*srt')).empty?
|
71
|
+
if @options.skip_subtitles != true && @options.download_subtitles && media.subtitle_streams.select { |s| @options.languages.include?(s.language.to_s) }.empty? && @adapter.list_files(media.original_filename.gsub(/.{4}$/, '.*srt')).empty?
|
72
72
|
@adapter.search_subtitles(media, @options.languages)
|
73
73
|
@adapter.download_subtitles(media, @options.languages)
|
74
74
|
end
|
@@ -104,6 +104,8 @@ module AppleTvConverter
|
|
104
104
|
media.number = @options.episode if @options.episode
|
105
105
|
media.use_absolute_episode_numbering = @options.use_absolute_numbering
|
106
106
|
media.episode_number_padding = @options.episode_number_padding if @options.episode_number_padding
|
107
|
+
media.movie_width = @options.width if @options.width > 0
|
108
|
+
media.movie_height = @options.height if @options.height > 0
|
107
109
|
end
|
108
110
|
end
|
109
111
|
end
|
@@ -10,7 +10,7 @@ module AppleTvConverter
|
|
10
10
|
|
11
11
|
def search_subtitles(media, languages)
|
12
12
|
# Load the subtitles into memory and get IMDB id from them
|
13
|
-
AppleTvConverter::SubtitlesFetcher::Opensubtitles.new(languages) do |fetcher|
|
13
|
+
AppleTvConverter::SubtitlesFetcher::Opensubtitles.new(languages, self.conversion_options.download_subtitles_username, self.conversion_options.download_subtitles_password) do |fetcher|
|
14
14
|
fetcher.search_subtitles media do |subtitles|
|
15
15
|
media.imdb_id = subtitles.first['IDMovieImdb'] if media.imdb_id.nil? || media.imdb_id.to_s.strip == ''
|
16
16
|
end
|
@@ -18,17 +18,37 @@ module AppleTvConverter
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def download_subtitles(media, languages)
|
21
|
-
AppleTvConverter::SubtitlesFetcher::Opensubtitles.new(languages) do |fetcher|
|
21
|
+
AppleTvConverter::SubtitlesFetcher::Opensubtitles.new(languages, self.conversion_options.download_subtitles_username, self.conversion_options.download_subtitles_password) do |fetcher|
|
22
22
|
if fetcher.has_found_subtitles?(media)
|
23
|
-
printf "* Downloading subtitles"
|
24
|
-
|
23
|
+
printf "* Downloading subtitles#{%Q[ using user "#{self.conversion_options.download_subtitles_username}"] unless self.conversion_options.download_subtitles_username.nil?}"
|
24
|
+
status = {
|
25
|
+
:total => 0,
|
26
|
+
:ok => 0,
|
27
|
+
:error => 0
|
28
|
+
}
|
29
|
+
|
30
|
+
fetcher.download_subtitles media do |step, subtitles, message|
|
25
31
|
case step
|
26
|
-
when :search
|
27
|
-
when :downloading
|
28
|
-
|
32
|
+
when :search then puts %Q[ (#{subtitles.map { |l, subs| "#{subs.count} #{AppleTvConverter.get_language_name(l)}" }.join(', ') })]
|
33
|
+
when :downloading
|
34
|
+
status[:total] += 1
|
35
|
+
printf " * Downloading: \##{subtitles['IDSubtitleFile']} (#{AppleTvConverter.get_language_name(subtitles['SubLanguageID'])}) - #{subtitles['SubFileName']}"
|
36
|
+
when :downloaded then
|
37
|
+
status[:ok] += 1
|
38
|
+
puts " [DONE]"
|
39
|
+
when :download_failed then
|
40
|
+
status[:error] += 1
|
41
|
+
puts " [ERROR - #{message}]"
|
29
42
|
end
|
30
43
|
end
|
31
|
-
|
44
|
+
|
45
|
+
if status[:total] == status[:ok]
|
46
|
+
puts " * All subtitles downloaded"
|
47
|
+
elsif status[:total] == status[:error]
|
48
|
+
puts " * Couldn't download any subtitle"
|
49
|
+
else
|
50
|
+
puts " * Downloaded #{status[:ok]} of #{status[:total]} subtitles"
|
51
|
+
end
|
32
52
|
else
|
33
53
|
puts "* No subtitles found to download"
|
34
54
|
end
|
@@ -71,7 +91,7 @@ module AppleTvConverter
|
|
71
91
|
end
|
72
92
|
|
73
93
|
def transcode(media, languages = nil)
|
74
|
-
if media.needs_transcoding?
|
94
|
+
if media.needs_transcoding? || needs_transformation?(media)
|
75
95
|
puts "* Transcoding"
|
76
96
|
|
77
97
|
options = {}
|
@@ -83,10 +103,7 @@ module AppleTvConverter
|
|
83
103
|
|
84
104
|
# Better video and audio transcoding quality
|
85
105
|
if media.needs_video_conversion?
|
86
|
-
#
|
87
|
-
dimensions = "-s #{(media.ffmpeg_data.width % 2 > 0) ? (media.ffmpeg_data.width + 1) : media.ffmpeg_data.width}x#{(media.ffmpeg_data.height % 2 > 0) ? (media.ffmpeg_data.height + 1) : media.ffmpeg_data.height}" if media.ffmpeg_data.width % 2 > 0 || media.ffmpeg_data.height % 2 > 0
|
88
|
-
|
89
|
-
options[:extra] << " #{dimensions} -mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -g 300 -pass 1 -q:v 1 -r 23.98"
|
106
|
+
options[:extra] << " #{get_transcoded_dimensions_options(media)} -mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -g 300 -pass 1 -q:v 1 -r 23.98 -pix_fmt yuv420p"
|
90
107
|
end
|
91
108
|
|
92
109
|
if media.needs_audio_conversion?
|
@@ -319,6 +336,10 @@ module AppleTvConverter
|
|
319
336
|
list_files(File.join(File.dirname(media.original_filename), '*.srt')).any?
|
320
337
|
end
|
321
338
|
|
339
|
+
def needs_transformation?(media)
|
340
|
+
media.needs_video_resizing?
|
341
|
+
end
|
342
|
+
|
322
343
|
def get_transcode_options(media)
|
323
344
|
options = " -vcodec #{media.needs_video_conversion? ? 'libx264' : 'copy'}"
|
324
345
|
options << " -acodec #{media.needs_audio_conversion? ? 'libfaac' : 'copy'}"
|
@@ -333,5 +354,9 @@ module AppleTvConverter
|
|
333
354
|
|
334
355
|
audio_channels
|
335
356
|
end
|
357
|
+
|
358
|
+
def get_transcoded_dimensions_options(media)
|
359
|
+
"-s #{(media.movie_width % 2 > 0) ? (media.movie_width + 1) : media.movie_width}x#{(media.movie_height % 2 > 0) ? (media.movie_height + 1) : media.movie_height}" if media.needs_video_resizing?
|
360
|
+
end
|
336
361
|
end
|
337
362
|
end
|
@@ -3,8 +3,10 @@ module AppleTvConverter
|
|
3
3
|
class Opensubtitles
|
4
4
|
attr_reader :languages, :token
|
5
5
|
|
6
|
-
def initialize(languages)
|
6
|
+
def initialize(languages, username = nil, password = nil)
|
7
7
|
@languages = languages
|
8
|
+
@username = username
|
9
|
+
@password = password
|
8
10
|
@server = XMLRPC::Client.new(SERVER, PATH, PORT)
|
9
11
|
@token = nil
|
10
12
|
|
@@ -73,8 +75,12 @@ module AppleTvConverter
|
|
73
75
|
media_subtitles.each do |language_code, subtitles|
|
74
76
|
subtitles.each do |subtitle|
|
75
77
|
block.call :downloading, subtitle
|
76
|
-
download_subtitle(media, subtitle)
|
77
|
-
|
78
|
+
result = download_subtitle(media, subtitle)
|
79
|
+
if result == true
|
80
|
+
block.call :downloaded, subtitle
|
81
|
+
else
|
82
|
+
block.call :download_failed, subtitle, result
|
83
|
+
end
|
78
84
|
end
|
79
85
|
end
|
80
86
|
end
|
@@ -92,7 +98,7 @@ module AppleTvConverter
|
|
92
98
|
def logged_in? ; return !@token.nil? ; end
|
93
99
|
|
94
100
|
def login
|
95
|
-
response = make_call("LogIn", '', '', '', USER_AGENT)
|
101
|
+
response = make_call("LogIn", @username || '', @password || '', '', USER_AGENT)
|
96
102
|
parse_response! response
|
97
103
|
|
98
104
|
@token = response['token'] if response[:success]
|
@@ -180,6 +186,10 @@ module AppleTvConverter
|
|
180
186
|
File.open(media.get_new_subtitle_filename(subtitle['SubLanguageID'], subtitle_data['idsubtitlefile']), 'wb') { |file| file.write(unzipped_data) }
|
181
187
|
end
|
182
188
|
end
|
189
|
+
|
190
|
+
return true
|
191
|
+
else
|
192
|
+
return response['status']
|
183
193
|
end
|
184
194
|
end
|
185
195
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apple-tv-converter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-06-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|