class AppleTvConverter::SubtitlesFetcher::Opensubtitles

Constants

PATH
PORT
SERVER

STATUS CODES 200 OK 206 Partial content; message 301 Moved (host) 401 Unauthorized 402 Subtitles has invalid format 403 SubHashes (content and sent subhash) are not same! 404 Subtitles has invalid language! 405 Not all mandatory parameters was specified 406 No session 407 Download limit reached 408 Invalid parameters 409 Method not found 410 Other or unknown error 411 Empty or invalid useragent 412 %s has invalid format (reason) 413 Invalid ImdbID 414 Unknown User Agent 415 Disabled user agent 503 Service Unavailable

USER_AGENT

Attributes

languages[R]
token[R]

Public Class Methods

new(languages, username = nil, password = nil) { |self| ... } click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 6
def initialize(languages, username = nil, password = nil)
  @languages = languages
  @username = username
  @password = password
  @server = XMLRPC::Client.new(SERVER, PATH, PORT)
  @token = nil

  if block_given?
    begin
      yield self
    rescue
      raise
    ensure
      logout
    end
  end
end

Private Class Methods

subtitles() click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 94
def self.subtitles
  @@subtitles ||= {}
end

Public Instance Methods

download_subtitles(media, &block) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 62
def download_subtitles(media, &block)
  return unless has_found_subtitles? media

  data = Opensubtitles.subtitles[media]
  media_subtitles = filter_subtitles(data, media)

  # If we have subtitles matched by moviehash, get only the first and ignore the rest
  # otherwise, get all
  media_subtitles = Hash[*media_subtitles.map { |language, subs| [language, [ subs.detect { |s| s['MatchedBy'] } || subs ].flatten ] }.flatten(1) ]

  block.call :search, media_subtitles if block

  # We now have one or many subtitles per language code, so start downloading
  media_subtitles.each do |language_code, subtitles|
    subtitles.each do |subtitle|
      block.call :downloading, subtitle
      result = download_subtitle(media, subtitle)
      if result == true
        block.call :downloaded, subtitle
      else
        block.call :download_failed, subtitle, result
      end
    end
  end
end
has_found_subtitles?(media) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 58
def has_found_subtitles?(media)
  (Opensubtitles.subtitles[media] && Opensubtitles.subtitles[media].any?) == true
end
logout() click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 24
def logout
  response = make_call("LogOut", get_token)
  parse_response! response
  @token = nil if response[:success]
end
search_subtitles(media, &block) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 30
def search_subtitles(media, &block)
  language_options = languages.map(&:to_sym).join(',') if languages.any?
  options = []

  # Query by movie hash
  options << {
    :moviehash => media.movie_hash.to_s,
    :moviebytesize => media.movie_file_size.to_s
  }
  # Query by movie name
  options << { :query => media.show }
  # and IMDB id if present
  options.last[:imdb_id] = media.imdb_id if media.imdb_id

  # Add common options
  options.each do |query_option|
    query_option[:sublanguageid] = language_options if language_options
    query_option[:season] = media.season if media.is_tv_show_episode?
    query_option[:episode] = media.number if media.is_tv_show_episode?
  end

  response = search_for_subtitles(media, options)
  if response[:success] && response['data']
    Opensubtitles.subtitles[media] = response['data']
    block.call response['data'] if block
  end
end
status() click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 88
def status
  make_call('ServerInfo')
end

Private Instance Methods

do_make_call(function, retries, *parameters) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 204
def do_make_call(function, retries, *parameters)
  begin
    # Flatten the parameters to the correct depth
    @server.call(*[function, parameters.flatten(1)].flatten(1))
  rescue EOFError => e
    if retries < 3
      # retry
      puts "Error (EOFError): retrying"
      do_make_call function, retries + 1, *parameters
    else
      puts "Error (EOFError): retried 3 times, giving up"
      raise e
    end
  rescue XMLRPC::FaultException => e
    puts "Error (XMLRPC::FaultException):"
    puts e.faultCode
    puts e.faultString
    raise e
  rescue RuntimeError => e
    if retries < 3
      # retry
      puts "Error (RuntimeError, most likely 503): retrying after 2 seconds"
      sleep 2
      do_make_call function, retries + 1, *parameters
    else
      puts "Error (RuntimeError, most likely 503): retried 3 times, giving up"

      raise e
    end
  rescue Exception => e
    puts "Error (#{e.class})"
    puts e.message

    raise e
  end
end
download(url) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 196
def download(url)
  Net::HTTP.get(URI.parse(url))
end
download_subtitle(media, subtitle) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 172
def download_subtitle(media, subtitle)
  response = make_call("DownloadSubtitles", get_token, [ subtitle['IDSubtitleFile'] ])
  parse_response! response

  if response[:success]
    data = response['data']

    if data
      data.each do |subtitle_data|
        # Decode Base64 encoded gzipped data
        zip_data = Base64.decode64(subtitle_data['data'])
        # UnGZip it
        unzipped_data = Zlib::GzipReader.new(StringIO.new(zip_data)).read
        # Write it to a new file
        File.open(media.get_new_subtitle_filename(subtitle['SubLanguageID'], subtitle_data['idsubtitlefile']), 'wb') { |file| file.write(unzipped_data) }
      end
    end

    return true
  else
    return response['status']
  end
end
filter_subtitles(data, media) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 114
def filter_subtitles(data, media)
  # "MatchedBy" -> "moviehash"
  # "MatchedBy" -> "imdbid"
  # "MatchedBy" -> "fulltext"

  # Define priorities by match type
  data.each do |s|
    s[:priority] = case s['MatchedBy']
      when 'moviehash'  then 100
      when 'imdbid'     then 200
      when 'fulltext'   then 300
      else                   400
    end
  end


  # Order the subtitles first by lowest priority (my match)
  # and then by download count (descending). This way, we'll get the
  # best, top downloaded match on top
  media_subtitles = data.sort { |c, d| [d[:priority], c['SubDownloadsCnt'].to_i] <=> [c[:priority], d['SubDownloadsCnt'].to_i] }.reverse


  # Get only unique subtitle entries (we can have more than one)
  # due to different 'MatchedBy'
  media_subtitles = media_subtitles.uniq { |s| s['IDSubtitle'] }

  # Filter by subtitles format (srt)
  media_subtitles = media_subtitles.select { |s| s['SubFormat'].downcase == 'srt' }
  # Filter by number of discs (1)
  media_subtitles = media_subtitles.select { |s| s['SubSumCD'] == '1' }
  # Filter by language
  media_subtitles = media_subtitles.select { |s| languages.empty? || languages.include?(s['SubLanguageID'].to_sym) }
  # Filter by movie name (unless it's an episode, as the movie name can be the episode's title)
  media_subtitles = media_subtitles.select { |s| s['MatchedBy'] == 'moviehash' || normalize(s['MovieName']) == normalize(media.show) } unless media.is_tv_show_episode?

  # exact_match = media_subtitles.select do |s|
  #   !File.basename(media.original_filename).downcase.index(s['MovieReleaseName'].downcase).nil? ||
  #   !File.basename(media.original_filename).downcase.index(s['SubFileName'].gsub(/\..*?$/, '').downcase).nil?
  # end

  # # We found exact matches on the movie name, so ignore the rest
  # media_subtitles = exact_match if exact_match.any?

  # Group the subtitles by language code
  media_subtitles = media_subtitles.group_by { |a| a['SubLanguageID'].to_sym }

  all_subtitles = Hash[*media_subtitles.flatten(1)]

  return all_subtitles
end
get_token() click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 107
def get_token
  login unless @token
  return @token
end
logged_in?() click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 98
def logged_in? ; return !@token.nil? ; end
login() click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 100
def login
  response = make_call("LogIn", @username || '', @password || '', '', USER_AGENT)
  parse_response! response

  @token = response['token'] if response[:success]
end
make_call(function, *parameters) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 200
def make_call(function, *parameters)
  do_make_call function, 0, parameters
end
normalize(string) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 112
def normalize(string) ; return string.gsub(/[^0-9a-z ]/i, '').gsub(/\s+/, ' ').downcase.strip ; end
parse_response!(response) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 242
def parse_response!(response)
  # Clear the token in case of some errors
  @token = nil if response['status'] =~ /^(?:401|406|411|414|415)/i
  response[:success] = !(response['status'] =~ /^(?:200|206)/).nil?
end
search_for_subtitles(media, options) click to toggle source
# File lib/apple_tv_converter/subtitles_fetcher/opensubtitles.rb, line 165
def search_for_subtitles(media, options)
  response = make_call("SearchSubtitles", get_token, options)
  parse_response! response

  return response
end