apple-tv-converter 0.4.4 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,7 +18,6 @@ module AppleTvConverter
18
18
  end
19
19
  rescue ArgumentError => e
20
20
  puts "Error: #{e.message}"
21
- puts e.backtrace
22
21
  rescue => e
23
22
  puts "Error: #{e.message}"
24
23
  puts e.backtrace
@@ -38,7 +37,9 @@ module AppleTvConverter
38
37
  options.skip_metadata = false
39
38
  options.skip_cleanup = false
40
39
  options.add_to_itunes = false
41
- options.check_imdb = false
40
+ options.skip_online_metadata = false
41
+ options.plex_format = false
42
+ options.interactive = true
42
43
  options.imdb_id = nil
43
44
  options.languages = []
44
45
  options.media = []
@@ -47,27 +48,17 @@ module AppleTvConverter
47
48
  opts.banner = "Usage: apple-tv-converter [options] [file]\n" +
48
49
  " [file] must be provided unless the -d (--dir) switch is present.\n"
49
50
 
50
- opts.on('--no-transcoding', "Don't transcode video or audio") do |v|
51
- options.skip_transcoding = true
52
- end
53
-
54
- opts.on('--no-subtitles', "Don't add subtitles") do |v|
55
- options.skip_subtitles = true
56
- end
57
-
58
- opts.on('--no-metadata', "Don't add metadata") do |m|
59
- options.skip_metadata = true
60
- end
61
-
62
- opts.on('--no-cleanup', "Don't cleanup the source files after processing") do |c|
63
- options.skip_cleanup = true
51
+ opts.on('-i', '--id id', "Set a specific id for fetching metadata from online services") do |id|
52
+ options.imdb_id = id
64
53
  end
65
54
 
66
55
  opts.on('-l', '--languages eng,por,...', Array, "Only keep audio and subtitles in the specified languages") do |languages|
67
56
  options.languages.push *languages
57
+ # If filtering by languages, always include the undetermined language
58
+ options.languages.push 'und' unless options.languages.include?('und')
68
59
  end
69
60
 
70
- opts.on('-d', '--dir [DIRECTORY]', 'Process all files in DIRECTORY recursively') do |dir|
61
+ opts.on('-d', '--dir DIRECTORY', 'Process all files in DIRECTORY recursively') do |dir|
71
62
  raise ArgumentError.new("Path not found: #{dir}") unless File.exists?(dir)
72
63
  raise ArgumentError.new("Path is not a directory: #{dir}") unless File.directory?(dir)
73
64
 
@@ -85,18 +76,56 @@ module AppleTvConverter
85
76
  options.add_to_itunes = true
86
77
  end
87
78
 
88
- opts.on('--imdb [ID]',
89
- "Gather data from IMDB (optionally specifying movie id. If an id isn't specified",
90
- "the program looks for a file on the same directory, named <id>.imdb)"
91
- ) do |id|
92
- options.check_imdb = true
93
- options.imdb_id = id if id
94
- end
95
-
96
79
  opts.on('--os', "Download subtitles and infer IMDB ID from opensubtitles.org") do |i|
97
80
  options.download_subtitles = true
98
81
  end
99
82
 
83
+ opts.on('--plex', 'Rename file(s) to Plex Media Server recommended format') do
84
+ options.plex_format = true
85
+ options.skip_online_metadata = false
86
+ end
87
+
88
+ opts.separator ""
89
+
90
+ opts.on('--no-transcoding', "Don't transcode video or audio") do |v|
91
+ options.skip_transcoding = true
92
+ end
93
+
94
+ opts.on('--no-subtitles', "Don't add subtitles") do |v|
95
+ options.skip_subtitles = true
96
+ end
97
+
98
+ opts.on('--no-metadata', "Don't add metadata (implies --no-online-metadata)") do |m|
99
+ options.skip_metadata = true
100
+ end
101
+
102
+ opts.on('--no-online-metadata', "Don't fetch metadata from online services (IMDB or TheTVDB)") do |m|
103
+ options.skip_online_metadata = true
104
+ end
105
+
106
+ opts.on('--no-interactive', "Perform all operations without user intervention, using sensible defaults") do |m|
107
+ options.interactive = false
108
+ end
109
+
110
+ opts.on('--no-cleanup', "Don't cleanup the source files after processing") do |c|
111
+ options.skip_cleanup = true
112
+ end
113
+
114
+ opts.separator ""
115
+ opts.separator "Other options:"
116
+
117
+ opts.on('-f', '--ffmpeg LOCATION', 'Set path to ffmpeg binary') do |f|
118
+ FFMPEG.ffmpeg_binary = f
119
+ end
120
+
121
+ opts.separator ""
122
+ opts.separator "DEPRECATED options:"
123
+
124
+ opts.on('--imdb', "Gather data from IMDB (optionally specifying movie id)") do
125
+ puts "Warning: Switch --imdb is DEPRECATED, and will be removed in a future version. It is now activated by default"
126
+ puts " If you want to specify an id, please use the switch --id."
127
+ end
128
+
100
129
  opts.separator ""
101
130
  opts.separator "Common options:"
102
131
 
@@ -131,16 +160,33 @@ module AppleTvConverter
131
160
  # [1] - Show name
132
161
  begin
133
162
  e = AppleTvConverter::Media.new
134
- # Extract name
135
- match = File.dirname(file).match(/.*\/(.*?)(?:S(\d+))?$/i)
163
+
164
+ # Extract name (check if the folder name is Season XX, and use the parent folder name if it is)
165
+ test_path = File.basename(File.dirname(file)) =~ /^season\s*\d+/i ? File.dirname(File.dirname(file)) : File.dirname(file)
166
+
167
+ match = test_path.match(/.*\/(.*?)(?:S(\d+))?$/i)
168
+
136
169
  e.show = match[1].strip
137
170
 
138
- # Extract season an media number
139
- match = File.basename(file).match(/.*?(?:S(\d+)E(\d+)|(\d+)x(\d+)).*/i)
171
+ # Extract season and media number
172
+ match = File.basename(file).match(/.*?S(\d+)E(\d+)(?:(?:[-E]+(\d+))*).*?/i)
173
+
174
+ # /.*?S(\d+)E(\d+)(?:(?:[-E]+(\d+))*).*?/ -> S00E01, S00E01(E02)+, S00E01(-E02)+, S00E01(-02)+
140
175
  if match
141
- e.season = (match[1] || match[3]).to_i
142
- e.number = (match[2] || match[4]).to_i
176
+ e.season = match[1].to_i
177
+ e.number = match[2].to_i
178
+ e.last_number = match[3].to_i if match[3]
179
+ else
180
+ match = File.basename(file).match(/(\d+)x(\d+)(?:(?:_?(?:\1)x(\d+))*)/i)
181
+
182
+ # /(\d+)x(\d+)(?:(?:_?(?:\1)x(\d+))*)/ -> 0x01, 0x01(_0x02)+ , assuming same season number (0x01_1x02 fails!)
183
+ if match
184
+ e.season = match[1].to_i
185
+ e.number = match[2].to_i
186
+ e.last_number = match[3].to_i if match[3]
187
+ end
143
188
  end
189
+
144
190
  e.original_filename = file
145
191
 
146
192
  return e
@@ -1,16 +1,14 @@
1
1
  module AppleTvConverter
2
2
  class Media
3
- attr_accessor :show, :season, :number
4
- attr_accessor :imdb_movie, :imdb_id
3
+ attr_accessor :show, :season, :number, :last_number
4
+ attr_accessor :imdb_movie, :imdb_id, :imdb_episode_id
5
+ attr_accessor :tvdb_movie
6
+ attr_accessor :network, :tvdb_id, :tvdb_season_id, :tvdb_episode_id, :first_air_date, :release_date, :episode_title
5
7
  attr_reader :original_filename
6
8
 
7
- def self.subtitle_extensions
8
- ['srt', 'sub', 'ssa', 'ass']
9
- end
9
+ def self.subtitle_extensions ; ['srt', 'sub', 'ssa', 'ass'] ; end
10
10
 
11
- def self.ignored_extensions
12
- ['nfo', 'jpg', 'png', 'bmp', 'sfv', 'imdb']
13
- end
11
+ def self.ignored_extensions ; ['nfo', 'jpg', 'png', 'bmp', 'sfv', 'imdb'] ; end
14
12
 
15
13
  def original_filename=(value)
16
14
  @original_filename = value
@@ -28,40 +26,51 @@ module AppleTvConverter
28
26
  @converted_filename = original_filename.gsub(File.extname(original_filename), "_2#{File.extname(original_filename)}")
29
27
  @converted_filename_equals_original_filename = true
30
28
  end
31
- end
32
29
 
33
- def converted_filename_equals_original_filename?
34
- @converted_filename_equals_original_filename || false
35
- end
30
+ load_data_file
36
31
 
37
- def artwork_filename
38
- @artwork_filename ||= self.original_filename.gsub(File.extname(self.original_filename), '.jpg')
32
+ @original_filename
39
33
  end
40
34
 
41
- def subtitle_filename
42
- @subtitle_filename ||= self.original_filename.gsub(File.extname(self.original_filename), '.srt')
43
- end
35
+ def converted_filename_equals_original_filename? ; @converted_filename_equals_original_filename || false ; end
44
36
 
45
- def converted_filename
46
- @converted_filename ||= self.original_filename.gsub(File.extname(self.original_filename), '.mp4')
47
- end
37
+ def artwork_filename ; @artwork_filename ||= self.original_filename.gsub(File.extname(self.original_filename), '.jpg') ; end
48
38
 
49
- def converted_filename=(value)
50
- @converted_filename = value
51
- end
39
+ def subtitle_filename ; @subtitle_filename ||= self.original_filename.gsub(File.extname(self.original_filename), '.srt') ; end
52
40
 
53
- def backup_filename
54
- @backup_filename ||= "#{self.original_filename}.backup"
55
- end
41
+ def converted_filename ; @converted_filename ||= self.original_filename.gsub(File.extname(self.original_filename), '.mp4') ; end
42
+
43
+ def converted_filename=(value) ; @converted_filename = value ; end
56
44
 
57
- def converted_filename_with_subtitles
58
- @converted_filename_with_subtitles ||= self.original_filename.gsub(/\.(mkv|avi|m4v)/, '_subtitled.mp4')
45
+ def base_location
46
+ unless @base_location
47
+ @base_location = File.dirname(original_filename)
48
+ @base_location = File.dirname(@base_location) if File.basename(@base_location) =~ /^season \d+$/i
49
+ end
50
+ @base_location
59
51
  end
60
52
 
61
- def ffmpeg_data
62
- @ffmpeg_data ||= FFMPEG::Movie.new(original_filename)
53
+ def data_file ; @data_file ||= File.join(base_location, '.apple-tv-converter.data') ; end
54
+ def has_data_file? ; File.exists?(data_file) ; end
55
+
56
+ def plex_format_filename
57
+ filename = if is_tv_show_episode?
58
+ %Q[#{show} - s#{season.to_s.rjust(2, '0')}e#{number.to_s.rjust(2, '0')}#{"-e#{last_number.to_s.rjust(2, '0')}" if last_number}#{" - #{episode_title.gsub(/\\|\//, '-').gsub(/\:/, '.').gsub(/&amp;/, '&').strip}" if !episode_title.nil? && !episode_title.blank?}.mp4]
59
+ else
60
+ "#{show} (#{release_date || imdb_movie.year}).mp4"
61
+ end
62
+
63
+ File.join(File.dirname(converted_filename), filename)
63
64
  end
64
65
 
66
+ def resulting_filename ; File.exists?(plex_format_filename) ? plex_format_filename : converted_filename ; end
67
+
68
+ def backup_filename ; @backup_filename ||= "#{self.original_filename}.backup" ; end
69
+
70
+ def converted_filename_with_subtitles ; @converted_filename_with_subtitles ||= self.original_filename.gsub(/\.(mkv|avi|m4v)/, '_subtitled.mp4') ;end
71
+
72
+ def ffmpeg_data ; @ffmpeg_data ||= FFMPEG::Movie.new(original_filename) ; end
73
+
65
74
  def quality
66
75
  if !@quality
67
76
  @quality = '1080p' if ffmpeg_data.height == 1080 || ffmpeg_data.width == 1920
@@ -72,37 +81,21 @@ module AppleTvConverter
72
81
  @quality
73
82
  end
74
83
 
75
- def genre
76
- is_tv_show_episode? ? show : "#{quality} Movies"
77
- end
84
+ def genre ; is_tv_show_episode? ? show : "#{quality} Movies" ; end
78
85
 
79
- def quality=(value)
80
- @quality = value
81
- end
86
+ def quality=(value) ; @quality = value ; end
82
87
 
83
- def name
84
- %Q[#{show}#{" S#{season.to_s.rjust(2, '0')}E#{number.to_s.rjust(2, '0')}" if is_tv_show_episode?}]
85
- end
88
+ def name ; %Q[#{show}#{" S#{season.to_s.rjust(2, '0')}E#{number.to_s.rjust(2, '0')}" if is_tv_show_episode?}] ; end
86
89
 
87
- def is_tv_show_episode?
88
- !season.nil? && !number.nil?
89
- end
90
+ def is_tv_show_episode? ; !season.nil? && !number.nil? ; end
90
91
 
91
- def is_movie?
92
- !is_tv_show_episode?
93
- end
92
+ def is_movie? ; !is_tv_show_episode? ; end
94
93
 
95
- def is_mp4?
96
- ffmpeg_data.container =~ /mp4/ rescue File.extname(original_filename) =~ /\.(m4v|mp4)$/
97
- end
94
+ def is_mp4? ; ffmpeg_data.container =~ /mp4/ rescue File.extname(original_filename) =~ /\.(m4v|mp4)$/ ; end
98
95
 
99
- def is_valid?
100
- ffmpeg_data.valid?
101
- end
96
+ def is_valid? ; ffmpeg_data.valid? ; end
102
97
 
103
- def backup!
104
- FileUtils.cp original_filename, backup_filename
105
- end
98
+ def backup! ; FileUtils.cp original_filename, backup_filename ; end
106
99
 
107
100
  def has_embedded_subtitles?(languages = [])
108
101
  languages = languages.map { |l| l.downcase.to_sym }
@@ -115,44 +108,41 @@ module AppleTvConverter
115
108
  return @streams
116
109
  end
117
110
 
118
- def video_streams
119
- streams :video
120
- end
111
+ def video_streams ; streams :video ; end
121
112
 
122
- def audio_streams
123
- streams :audio
124
- end
113
+ def audio_streams ; streams :audio ; end
125
114
 
126
- def subtitle_streams
127
- streams :subtitle
128
- end
115
+ def subtitle_streams ; streams :subtitle ; end
129
116
 
130
- def needs_audio_conversion?
131
- return ffmpeg_data.audio_codec !~ /(?:aac)/i
132
- end
117
+ def needs_audio_conversion? ; return ffmpeg_data.audio_codec !~ /(?:aac)/i ; end
133
118
 
134
- def needs_video_conversion?
135
- return ffmpeg_data.video_codec !~ /(?:.*?h264|^mpeg4).*/i || ffmpeg_data.video_codec =~ /.*(?:xvid|divx).*/i
136
- end
119
+ def needs_video_conversion? ; return ffmpeg_data.video_codec !~ /(?:.*?h264|^mpeg4).*/i || ffmpeg_data.video_codec =~ /.*(?:xvid|divx).*/i ; end
137
120
 
138
- def needs_subtitles_conversion?
139
- return ffmpeg_data.subtitle_streams.any?
140
- end
121
+ def needs_subtitles_conversion? ; return ffmpeg_data.subtitle_streams.any? ; end
141
122
 
142
- def needs_transcoding?
143
- !(is_valid? && is_mp4? && !needs_video_conversion? && !needs_audio_conversion?)
144
- end
123
+ def needs_transcoding? ; !(is_valid? && is_mp4? && !needs_video_conversion? && !needs_audio_conversion?) ; end
145
124
 
146
- def hd?
147
- ['1080p', '720p'].include?(quality)
148
- end
125
+ def hd? ; ['1080p', '720p'].include?(quality) ; end
149
126
 
150
- def movie_file_size
151
- @movie_file_size ||= File.size(original_filename)
127
+ def movie_file_size ; @movie_file_size ||= File.size(original_filename) ; end
128
+
129
+ def movie_hash ; @movie_hash ||= AppleTvConverter::MovieHasher.compute_hash(original_filename) ; end
130
+
131
+ def tvdb_movie_data(key, default = nil) ;
132
+ 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
133
+ return default
152
134
  end
153
135
 
154
- def movie_hash
155
- @movie_hash ||= AppleTvConverter::MovieHasher.compute_hash(original_filename)
136
+ def tvdb_movie_poster
137
+ local_file = AppleTvConverter::TvDbFetcher.get_poster(self)
138
+
139
+ unless File.exists?(local_file)
140
+ artwork_filename = imdb_movie.poster if imdb_movie && imdb_movie.poster
141
+
142
+ AppleTvConverter.copy artwork_filename, local_file unless artwork_filename.blank?
143
+ end
144
+
145
+ local_file
156
146
  end
157
147
 
158
148
  def get_new_subtitle_filename(language, subid = nil)
@@ -160,5 +150,40 @@ module AppleTvConverter
160
150
  existing_subtitle_counter = subid.nil? ? Dir[File.join(dir_name, '*.srt')].length : subid
161
151
  return File.join(dir_name, File.basename(original_filename).gsub(File.extname(original_filename), ".#{existing_subtitle_counter}.#{language}.srt"))
162
152
  end
153
+
154
+ def update_data_file!
155
+ data = has_data_file? ? YAML.load_file(data_file) : {}
156
+
157
+ data[:tvdb_id] = self.tvdb_id if self.tvdb_id
158
+ data[:imdb_id] = self.imdb_id if self.imdb_id
159
+
160
+ if self.is_tv_show_episode?
161
+ episode_data = {}
162
+ episode_data[:tvdb_episode_id] = self.tvdb_episode_id if self.tvdb_episode_id
163
+ episode_data[:tvdb_season_id] = self.tvdb_season_id if self.tvdb_season_id
164
+ episode_data[:imdb_episode_id] = self.imdb_episode_id if self.imdb_episode_id
165
+
166
+ data[:episodes] ||= {}
167
+ data[:episodes][:"s#{self.season}e#{self.number}"] = episode_data
168
+ end
169
+
170
+ File.open(self.data_file, 'w') do |f|
171
+ f.write data.to_yaml
172
+ end
173
+ end
174
+
175
+ private
176
+
177
+ def load_data_file
178
+ begin
179
+ if has_data_file?
180
+ data = YAML.load_file(data_file)
181
+ self.tvdb_id = data[:tvdb_id] if data.has_key?(:tvdb_id)
182
+ self.imdb_id = data[:imdb_id] if data.has_key?(:imdb_id)
183
+ end
184
+ rescue => e
185
+ ap ['e', e]
186
+ end
187
+ end
163
188
  end
164
189
  end
@@ -1,15 +1,10 @@
1
1
  module AppleTvConverter
2
2
  class MediaConverter
3
- include AppleTvConverter
4
-
5
3
  @@timeout = 200
6
4
 
7
- def is_windows? ; RUBY_PLATFORM =~/.*?mingw.*?/i ; end
8
- def is_macosx? ; RUBY_PLATFORM =~/.*?darwin.*?/i ; end
9
-
10
5
  def initialize(options)
11
6
  @options = options
12
- @adapter = is_windows? ? AppleTvConverter::MediaConverterWindowsAdapter.new : AppleTvConverter::MediaConverterMacAdapter.new
7
+ @adapter = (AppleTvConverter.is_windows? ? AppleTvConverter::MediaConverterWindowsAdapter : AppleTvConverter::MediaConverterMacAdapter).new(options)
13
8
 
14
9
  AppleTvConverter.logger.level = Logger::ERROR
15
10
  FFMPEG.logger.level = Logger::ERROR
@@ -18,31 +13,32 @@ module AppleTvConverter
18
13
 
19
14
  def process_media(media)
20
15
  # Load IMDB id from options
21
- media.imdb_id = @options.imdb_id
16
+ media.imdb_id ||= @options.imdb_id
22
17
 
23
- # Start searching subtitles if we either need to download them, or we need the IMDB id
24
- if (@options.skip_subtitles != true && @options.download_subtitles && media.subtitle_streams.empty? && @adapter.list_files(media.original_filename.gsub(/.{4}$/, '.*srt')).empty?) ||
25
- !(@options.skip_metadata || !@options.check_imdb)
26
- @adapter.search_subtitles(media, @options.languages)
27
- end
28
18
 
29
19
  if media.is_tv_show_episode?
30
20
  puts "* TV Show Episode information:"
31
21
  puts "* Name: #{media.show}"
32
22
  puts "* Season: #{media.season}"
33
- puts "* Number: #{media.number}"
23
+ puts %Q[* Number: #{media.number}#{"-#{media.last_number}" if media.last_number}]
34
24
  else
35
25
  puts "* Movie information"
36
26
  puts "* Name: #{media.show}"
37
27
  puts "* Genre: #{media.genre}"
38
28
  end
39
- puts "* IMDB ID: #{media.imdb_id}" if media.imdb_id
29
+ if !@options.skip_online_metadata
30
+ if media.imdb_id
31
+ puts "* IMDB ID: #{media.imdb_id}"
32
+ elsif !@options.skip_metadata
33
+ puts "* IMDB ID: Unknown yet"
34
+ end
35
+ end
40
36
 
41
37
  puts "* #{media.audio_streams.length} audio track(s)"
42
38
  if media.audio_streams.any?
43
39
  media.audio_streams.each do |audio|
44
40
  language_code = audio.language || 'und'
45
- language_name = get_language_name(language_code)
41
+ language_name = AppleTvConverter.get_language_name(language_code)
46
42
  puts " * #{language_code} - #{language_name.nil? ? 'Unknown (ignoring)' : language_name}"
47
43
  end
48
44
  end
@@ -51,7 +47,7 @@ module AppleTvConverter
51
47
  if media.subtitle_streams.any?
52
48
  media.subtitle_streams.each do |subtitle|
53
49
  language_code = subtitle.language || 'und'
54
- language_name = get_language_name(language_code)
50
+ language_name = AppleTvConverter.get_language_name(language_code)
55
51
  puts " * #{language_code} - #{language_name.nil? ? 'Unknown (ignoring)' : language_name}"
56
52
  end
57
53
  end
@@ -61,12 +57,13 @@ module AppleTvConverter
61
57
  @adapter.list_files(media.original_filename.gsub(/.{4}$/, '.*srt')).each do |subtitle|
62
58
  subtitle =~ /\.(.{3})\.srt/i
63
59
  language_code = $1 || 'und'
64
- language_name = get_language_name(language_code)
60
+ language_name = AppleTvConverter.get_language_name(language_code)
65
61
  puts " * #{language_code.blank? ? 'eng' : language_code} - #{language_name.nil? ? 'Unknown (ignoring)' : language_name}"
66
62
  end
67
63
  end
68
64
 
69
65
  if @options.skip_subtitles != true && @options.download_subtitles && media.subtitle_streams.empty? && @adapter.list_files(media.original_filename.gsub(/.{4}$/, '.*srt')).empty?
66
+ @adapter.search_subtitles(media, @options.languages)
70
67
  @adapter.download_subtitles(media, @options.languages)
71
68
  end
72
69
 
@@ -74,15 +71,19 @@ module AppleTvConverter
74
71
 
75
72
  if @options.skip_transcoding || @adapter.transcode(media, @options.languages)
76
73
  @adapter.add_subtitles(media) unless @options.skip_subtitles
77
- unless @options.skip_metadata || !@options.check_imdb
74
+
75
+ unless @options.skip_metadata || @options.skip_online_metadata
78
76
  media.imdb_id ||= @options.imdb_id
79
- @adapter.get_imdb_info(media)
77
+ @adapter.get_metadata(media)
80
78
  end
81
- @adapter.tag(media) unless @options.skip_metadata
82
79
 
83
- @adapter.add_to_itunes media if @options.add_to_itunes
84
- @adapter.clean_up(media) unless @options.skip_cleanup
80
+ @adapter.tag media unless @options.skip_metadata
81
+ @adapter.clean_up media unless @options.skip_cleanup
82
+ @adapter.rename_to_plex_format media if @options.plex_format
83
+ @adapter.add_to_itunes media if @options.add_to_itunes
85
84
  end
85
+
86
+ media.update_data_file!
86
87
  end
87
88
  end
88
- end
89
+ end