|
|
@ -53,7 +53,18 @@ class TitulkySubtitle(Subtitle):
|
|
|
|
self.download_link = download_link
|
|
|
|
self.download_link = download_link
|
|
|
|
self.skip_wrong_fps = skip_wrong_fps
|
|
|
|
self.skip_wrong_fps = skip_wrong_fps
|
|
|
|
self.matches = None
|
|
|
|
self.matches = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Try to parse S00E00 string from the main subtitle name
|
|
|
|
|
|
|
|
season_episode_string = re.findall('S(\d+)E(\d+)', self.names[0], re.IGNORECASE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# If we did not search for subtitles with season and episode numbers in search query,
|
|
|
|
|
|
|
|
# try to parse it from the main subtitle name that most likely contains it
|
|
|
|
|
|
|
|
if season_episode_string:
|
|
|
|
|
|
|
|
if self.season is None:
|
|
|
|
|
|
|
|
self.season = int(season_episode_string[0][0])
|
|
|
|
|
|
|
|
if self.episode is None:
|
|
|
|
|
|
|
|
self.episode = int(season_episode_string[0][1])
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def id(self):
|
|
|
|
def id(self):
|
|
|
|
return self.sub_id
|
|
|
|
return self.sub_id
|
|
|
@ -61,41 +72,54 @@ class TitulkySubtitle(Subtitle):
|
|
|
|
def get_fps(self):
|
|
|
|
def get_fps(self):
|
|
|
|
return self.fps
|
|
|
|
return self.fps
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_matches(self, video):
|
|
|
|
def get_matches(self, video):
|
|
|
|
matches = set()
|
|
|
|
matches = set()
|
|
|
|
_type = 'movie' if isinstance(video, Movie) else 'episode'
|
|
|
|
_type = 'movie' if isinstance(video, Movie) else 'episode'
|
|
|
|
|
|
|
|
|
|
|
|
if _type == 'episode':
|
|
|
|
if _type == 'episode':
|
|
|
|
## EPISODE
|
|
|
|
## EPISODE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# match season/episode
|
|
|
|
if self.season and self.season == video.season:
|
|
|
|
if self.season and self.season == video.season:
|
|
|
|
matches.add('season')
|
|
|
|
matches.add('season')
|
|
|
|
if self.episode and self.episode == video.episode:
|
|
|
|
if self.episode and self.episode == video.episode:
|
|
|
|
matches.add('episode')
|
|
|
|
matches.add('episode')
|
|
|
|
|
|
|
|
|
|
|
|
name_matches = [video.series and sanitize(name) in sanitize(video.series) for name in self.names]
|
|
|
|
# match series name
|
|
|
|
if any(name_matches):
|
|
|
|
series_names = [video.series] + video.alternative_series
|
|
|
|
|
|
|
|
if _contains_element(_from=series_names, _in=self.names):
|
|
|
|
matches.add('series')
|
|
|
|
matches.add('series')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# match episode title
|
|
|
|
|
|
|
|
episode_titles = [video.title]
|
|
|
|
|
|
|
|
if _contains_element(_from=episode_titles, _in=self.names):
|
|
|
|
|
|
|
|
matches.add('title')
|
|
|
|
|
|
|
|
|
|
|
|
elif _type == 'movie':
|
|
|
|
elif _type == 'movie':
|
|
|
|
## MOVIE
|
|
|
|
## MOVIE
|
|
|
|
name_matches = [video.title and sanitize(name) in sanitize(video.title) for name in self.names]
|
|
|
|
|
|
|
|
if any(name_matches):
|
|
|
|
# match movie title
|
|
|
|
|
|
|
|
video_titles = [video.title] + video.alternative_titles
|
|
|
|
|
|
|
|
if _contains_element(_from=video_titles, _in=self.names):
|
|
|
|
matches.add('title')
|
|
|
|
matches.add('title')
|
|
|
|
|
|
|
|
|
|
|
|
## MOVIE OR EPISODE
|
|
|
|
## MOVIE OR EPISODE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# match year
|
|
|
|
if video.year and video.year == self.year:
|
|
|
|
if video.year and video.year == self.year:
|
|
|
|
matches.add('year')
|
|
|
|
matches.add('year')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# match other properties based on release info
|
|
|
|
matches |= guess_matches(video, guessit(self.release_info, {"type": _type}))
|
|
|
|
matches |= guess_matches(video, guessit(self.release_info, {"type": _type}))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# If turned on in settings, then do not match if video FPS is not equal to subtitle FPS
|
|
|
|
if self.skip_wrong_fps and video.fps and self.fps and not framerate_equal(video.fps, self.fps):
|
|
|
|
if self.skip_wrong_fps and video.fps and self.fps and not framerate_equal(video.fps, self.fps):
|
|
|
|
logger.info(f"Titulky.com: Skipping subtitle {self}: wrong FPS")
|
|
|
|
logger.info(f"Titulky.com: Skipping subtitle {self}: wrong FPS")
|
|
|
|
matches.clear()
|
|
|
|
matches.clear()
|
|
|
|
|
|
|
|
|
|
|
|
self.matches = matches
|
|
|
|
self.matches = matches
|
|
|
|
|
|
|
|
|
|
|
|
return matches
|
|
|
|
return matches
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -317,7 +341,7 @@ class TitulkyProvider(Provider):
|
|
|
|
'year': year
|
|
|
|
'year': year
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def process_row(self, row, keyword, thread_id=None, threads_data=None):
|
|
|
|
def process_row(self, row, video_names, thread_id=None, threads_data=None):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
# The first anchor tag is an image preview, the second is the name
|
|
|
|
# The first anchor tag is an image preview, the second is the name
|
|
|
|
anchor_tag = row.find_all('a')[1]
|
|
|
|
anchor_tag = row.find_all('a')[1]
|
|
|
@ -330,19 +354,20 @@ class TitulkyProvider(Provider):
|
|
|
|
# Approved subtitles have a pbl1 class for their row, others have a pbl0 class
|
|
|
|
# Approved subtitles have a pbl1 class for their row, others have a pbl0 class
|
|
|
|
approved = True if 'pbl1' in row.get('class') else False
|
|
|
|
approved = True if 'pbl1' in row.get('class') else False
|
|
|
|
|
|
|
|
|
|
|
|
# Name + alternative names
|
|
|
|
# Subtitle name + its alternative names
|
|
|
|
table_columns = row.findAll("td")
|
|
|
|
table_columns = row.findAll("td")
|
|
|
|
main_name = anchor_tag.get_text(strip=True)
|
|
|
|
main_sub_name = anchor_tag.get_text(strip=True)
|
|
|
|
alt_names = [alt_name.strip() for alt_name in table_columns[2].get_text(strip=True).split("/")]
|
|
|
|
alt_sub_names = [alt_sub_name.strip() for alt_sub_name in table_columns[2].get_text(strip=True).split("/")]
|
|
|
|
names = [main_name] + alt_names
|
|
|
|
sub_names = [main_sub_name] + alt_sub_names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Does at least one subtitle name contain one of the video names?
|
|
|
|
# Loop over all subtitle names and check if the keyword contains them
|
|
|
|
# Skip subtitles that do not match
|
|
|
|
name_matches = [keyword and sanitize(keyword) not in sanitize(name) for name in names]
|
|
|
|
# Video names -> the main title and alternative titles of a movie or an episode and so on...
|
|
|
|
|
|
|
|
# Subtitle names -> the main name and alternative names of a subtitle displayed in search results.
|
|
|
|
# Skip subtitles that do not contain the keyword in their name(s)
|
|
|
|
# Could be handled in TitulkySubtitle class, however we want to keep the number of requests
|
|
|
|
if keyword and all(name_matches) is False:
|
|
|
|
# as low as possible and this prevents the from requesting the details page unnecessarily
|
|
|
|
logger.debug(f"Titulky.com: Skipping subtitle with names: '{names}', because it does not not contain the keyword: '{keyword}'")
|
|
|
|
if not _contains_element(_from=video_names, _in=sub_names):
|
|
|
|
|
|
|
|
logger.debug(f"Titulky.com: Skipping subtitle with names: {sub_names}, because there was no match with video names: {video_names}")
|
|
|
|
if type(threads_data) is list and type(thread_id) is int:
|
|
|
|
if type(threads_data) is list and type(thread_id) is int:
|
|
|
|
threads_data[thread_id] = {
|
|
|
|
threads_data[thread_id] = {
|
|
|
|
'sub_info': None,
|
|
|
|
'sub_info': None,
|
|
|
@ -364,7 +389,7 @@ class TitulkyProvider(Provider):
|
|
|
|
|
|
|
|
|
|
|
|
# Combine all subtitle data into one dict
|
|
|
|
# Combine all subtitle data into one dict
|
|
|
|
result = {
|
|
|
|
result = {
|
|
|
|
'names': names,
|
|
|
|
'names': sub_names,
|
|
|
|
'id': sub_id,
|
|
|
|
'id': sub_id,
|
|
|
|
'approved': approved,
|
|
|
|
'approved': approved,
|
|
|
|
'details_link': details_link,
|
|
|
|
'details_link': details_link,
|
|
|
@ -411,7 +436,7 @@ class TitulkyProvider(Provider):
|
|
|
|
# - Subtitles are here categorised by seasons and episodes
|
|
|
|
# - Subtitles are here categorised by seasons and episodes
|
|
|
|
# - URL: https://premium.titulky.com/?action=serial&step=<SEASON>&id=<IMDB ID>
|
|
|
|
# - URL: https://premium.titulky.com/?action=serial&step=<SEASON>&id=<IMDB ID>
|
|
|
|
# - it seems that the url redirects to a page with their own internal ID, redirects should be allowed here
|
|
|
|
# - it seems that the url redirects to a page with their own internal ID, redirects should be allowed here
|
|
|
|
def query(self, language, type, keyword=None, year=None, season=None, episode=None, imdb_id=None):
|
|
|
|
def query(self, language, video_names, type, keyword=None, year=None, season=None, episode=None, imdb_id=None):
|
|
|
|
## Build the search URL
|
|
|
|
## Build the search URL
|
|
|
|
params = {}
|
|
|
|
params = {}
|
|
|
|
|
|
|
|
|
|
|
@ -483,23 +508,14 @@ class TitulkyProvider(Provider):
|
|
|
|
# Process the rows sequentially
|
|
|
|
# Process the rows sequentially
|
|
|
|
logger.info("Titulky.com: processing results in sequence")
|
|
|
|
logger.info("Titulky.com: processing results in sequence")
|
|
|
|
for i, row in enumerate(rows):
|
|
|
|
for i, row in enumerate(rows):
|
|
|
|
sub_info = self.process_row(row, keyword)
|
|
|
|
sub_info = self.process_row(row, video_names)
|
|
|
|
|
|
|
|
|
|
|
|
# If subtitle info was returned, then everything was okay
|
|
|
|
# If subtitle info was returned, then everything was okay
|
|
|
|
# and we can instationate it and add it to the list
|
|
|
|
# and we can instationate it and add it to the list
|
|
|
|
if sub_info:
|
|
|
|
if sub_info:
|
|
|
|
logger.debug(f"Titulky.com: Sucessfully retrieved subtitle info, row: {i}")
|
|
|
|
logger.debug(f"Titulky.com: Sucessfully retrieved subtitle info, row: {i}")
|
|
|
|
|
|
|
|
|
|
|
|
# Try to parse S00E00 string from the main subtitle name
|
|
|
|
subtitle_instance = self.subtitle_class(sub_info['id'], sub_info['language'], sub_info['names'], season, episode, sub_info['year'], sub_info['release'], sub_info['fps'],
|
|
|
|
sub_season = None
|
|
|
|
|
|
|
|
sub_episode = None
|
|
|
|
|
|
|
|
season_episode_string = re.findall('S(\d+)E(\d+)', sub_info['names'][0], re.IGNORECASE)
|
|
|
|
|
|
|
|
if season_episode_string:
|
|
|
|
|
|
|
|
sub_season = season_episode_string[0][0]
|
|
|
|
|
|
|
|
sub_episode = season_episode_string[0][1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
subtitle_instance = self.subtitle_class(sub_info['id'], sub_info['language'], sub_info['names'], sub_season, sub_episode, sub_info['year'], sub_info['release'], sub_info['fps'],
|
|
|
|
|
|
|
|
sub_info['uploader'], sub_info['approved'], sub_info['details_link'], sub_info['download_link'], skip_wrong_fps=self.skip_wrong_fps)
|
|
|
|
sub_info['uploader'], sub_info['approved'], sub_info['details_link'], sub_info['download_link'], skip_wrong_fps=self.skip_wrong_fps)
|
|
|
|
subtitles.append(subtitle_instance)
|
|
|
|
subtitles.append(subtitle_instance)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
@ -527,7 +543,7 @@ class TitulkyProvider(Provider):
|
|
|
|
# Row number j
|
|
|
|
# Row number j
|
|
|
|
logger.debug(f"Titulky.com: Creating thread {j} (batch: {i})")
|
|
|
|
logger.debug(f"Titulky.com: Creating thread {j} (batch: {i})")
|
|
|
|
# Create a thread for row j and start it
|
|
|
|
# Create a thread for row j and start it
|
|
|
|
threads[j] = Thread(target=self.process_row, args=[rows[j], keyword], kwargs={'thread_id': j, 'threads_data': threads_data})
|
|
|
|
threads[j] = Thread(target=self.process_row, args=[rows[j], video_names], kwargs={'thread_id': j, 'threads_data': threads_data})
|
|
|
|
threads[j].start()
|
|
|
|
threads[j].start()
|
|
|
|
|
|
|
|
|
|
|
|
# Wait for all created threads to finish before moving to another batch of rows
|
|
|
|
# Wait for all created threads to finish before moving to another batch of rows
|
|
|
@ -555,15 +571,7 @@ class TitulkyProvider(Provider):
|
|
|
|
logger.debug(f"Titulky.com: Sucessfully retrieved subtitle info, thread ID: {i}")
|
|
|
|
logger.debug(f"Titulky.com: Sucessfully retrieved subtitle info, thread ID: {i}")
|
|
|
|
sub_info = thread_data['sub_info']
|
|
|
|
sub_info = thread_data['sub_info']
|
|
|
|
|
|
|
|
|
|
|
|
# Try to parse S00E00 string from the main subtitle name
|
|
|
|
subtitle_instance = self.subtitle_class(sub_info['id'], sub_info['language'], sub_info['names'], season, episode, sub_info['year'], sub_info['release'], sub_info['fps'],
|
|
|
|
sub_season = None
|
|
|
|
|
|
|
|
sub_episode = None
|
|
|
|
|
|
|
|
season_episode_string = re.findall('S(\d+)E(\d+)', sub_info['names'][0], re.IGNORECASE)
|
|
|
|
|
|
|
|
if season_episode_string:
|
|
|
|
|
|
|
|
sub_season = season_episode_string[0][0]
|
|
|
|
|
|
|
|
sub_episode = season_episode_string[0][1]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
subtitle_instance = self.subtitle_class(sub_info['id'], sub_info['language'], sub_info['names'], sub_season, sub_episode, sub_info['year'], sub_info['release'], sub_info['fps'],
|
|
|
|
|
|
|
|
sub_info['uploader'], sub_info['approved'], sub_info['details_link'], sub_info['download_link'], skip_wrong_fps=self.skip_wrong_fps)
|
|
|
|
sub_info['uploader'], sub_info['approved'], sub_info['details_link'], sub_info['download_link'], skip_wrong_fps=self.skip_wrong_fps)
|
|
|
|
subtitles.append(subtitle_instance)
|
|
|
|
subtitles.append(subtitle_instance)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
@ -589,32 +597,36 @@ class TitulkyProvider(Provider):
|
|
|
|
|
|
|
|
|
|
|
|
for language in languages:
|
|
|
|
for language in languages:
|
|
|
|
if isinstance(video, Episode):
|
|
|
|
if isinstance(video, Episode):
|
|
|
|
|
|
|
|
video_names = [video.series, video.title] + video.alternative_series
|
|
|
|
|
|
|
|
|
|
|
|
# (1)
|
|
|
|
# (1)
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by IMDB ID (1)")
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by IMDB ID, Season and Episode (1)")
|
|
|
|
if video.series_imdb_id:
|
|
|
|
if video.series_imdb_id:
|
|
|
|
partial_subs = self.query(language, 'episode', imdb_id=video.series_imdb_id, season=video.season, episode=video.episode)
|
|
|
|
partial_subs = self.query(language, video_names, 'episode', imdb_id=video.series_imdb_id, season=video.season, episode=video.episode)
|
|
|
|
if(len(partial_subs) > 0):
|
|
|
|
if(len(partial_subs) > 0):
|
|
|
|
subtitles += partial_subs
|
|
|
|
subtitles += partial_subs
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# (2)
|
|
|
|
# (2)
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by keyword (2)")
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by keyword, Season and Episode (2)")
|
|
|
|
keyword = video.series
|
|
|
|
keyword = video.series
|
|
|
|
partial_subs = self.query(language, 'episode', keyword=keyword, season=video.season, episode=video.episode)
|
|
|
|
partial_subs = self.query(language, video_names, 'episode', keyword=keyword, season=video.season, episode=video.episode)
|
|
|
|
if(len(partial_subs) > 0):
|
|
|
|
if(len(partial_subs) > 0):
|
|
|
|
subtitles += partial_subs
|
|
|
|
subtitles += partial_subs
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# (3)
|
|
|
|
# (3)
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by keyword (3)")
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by keyword only (3)")
|
|
|
|
keyword = f"{video.series} S{video.season:02d}E{video.episode:02d}"
|
|
|
|
keyword = f"{video.series} S{video.season:02d}E{video.episode:02d}"
|
|
|
|
partial_subs = self.query(language, 'episode', keyword=keyword)
|
|
|
|
partial_subs = self.query(language, video_names, 'episode', keyword=keyword)
|
|
|
|
subtitles += partial_subs
|
|
|
|
subtitles += partial_subs
|
|
|
|
elif isinstance(video, Movie):
|
|
|
|
elif isinstance(video, Movie):
|
|
|
|
|
|
|
|
video_names = [video.title] + video.alternative_titles
|
|
|
|
|
|
|
|
|
|
|
|
# (1)
|
|
|
|
# (1)
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by IMDB ID (1)")
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by IMDB ID (1)")
|
|
|
|
if video.imdb_id:
|
|
|
|
if video.imdb_id:
|
|
|
|
partial_subs = self.query(language, 'movie', imdb_id=video.imdb_id)
|
|
|
|
partial_subs = self.query(language, video_names, 'movie', imdb_id=video.imdb_id)
|
|
|
|
if(len(partial_subs) > 0):
|
|
|
|
if(len(partial_subs) > 0):
|
|
|
|
subtitles += partial_subs
|
|
|
|
subtitles += partial_subs
|
|
|
|
continue
|
|
|
|
continue
|
|
|
@ -622,7 +634,7 @@ class TitulkyProvider(Provider):
|
|
|
|
# (2)
|
|
|
|
# (2)
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by keyword (2)")
|
|
|
|
logger.debug("Titulky.com: Finding subtitles by keyword (2)")
|
|
|
|
keyword = video.title
|
|
|
|
keyword = video.title
|
|
|
|
partial_subs = self.query(language, 'movie', keyword=keyword)
|
|
|
|
partial_subs = self.query(language, video_names, 'movie', keyword=keyword)
|
|
|
|
subtitles += partial_subs
|
|
|
|
subtitles += partial_subs
|
|
|
|
|
|
|
|
|
|
|
|
return subtitles
|
|
|
|
return subtitles
|
|
|
@ -671,3 +683,16 @@ def _get_subtitle_from_archive(archive):
|
|
|
|
return archive.read(name)
|
|
|
|
return archive.read(name)
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Check if any element from source array is **contained** in any element from target array
|
|
|
|
|
|
|
|
# Returns on the first match
|
|
|
|
|
|
|
|
def _contains_element(_from=None, _in=None):
|
|
|
|
|
|
|
|
source_array = _from
|
|
|
|
|
|
|
|
target_array = _in
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for source in source_array:
|
|
|
|
|
|
|
|
for target in target_array:
|
|
|
|
|
|
|
|
if sanitize(source) in sanitize(target):
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return False
|