Skip to content

Commit

Permalink
release: v3.17.0
Browse files Browse the repository at this point in the history
  • Loading branch information
xZetsubou committed Oct 1, 2024
1 parent db57a7a commit de790ee
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 72 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
* [v3.17.0](https://github.com/newt-sc/a4kSubtitles/releases/tag/service.subtitles.a4ksubtitles%2Fservice.subtitles.a4ksubtitles-3.17.0):
* Fix TV year sometimes being pulled incorrectly.
* Fix file name in both the temp directory and media directory.
* Fix subtitle file missing extension.
* Fix selecting the incorrect episode if the downloaded archive contains multiple subtitles.
* Fix auto-download not working after the first selected episode in the playlist (e.g., "play from here"; A4K only works for the first media).
* Fix subtitle unable to import due to "illegal characters"
* Improve subtitle episode determination.
* Improve results parsing.
* Now, auto-download will also copy the subtitle next to the video or to a custom location, depending on Kodi's subtitle storage mode path.
* SubSource: Now will also pull series that are in "absolute order," as some anime on the websites uses absolute order.
* SubSource: Fix an issue of duplicated subtitle IDs with different names. This can happen if a subtitle has multiple release name.

* [v3.16.1](https://github.com/newt-sc/a4kSubtitles/releases/tag/service.subtitles.a4ksubtitles%2Fservice.subtitles.a4ksubtitles-3.16.1):
* Fix addons.xml.crc

Expand Down
44 changes: 35 additions & 9 deletions a4kSubtitles/download.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
subtitles_exts = ['.srt', '.sub']
subtitles_exts_secondary = ['.smi', '.ssa', '.aqt', '.jss', '.ass', '.rt', '.txt']

def __download(core, filepath, request):
request['stream'] = True
Expand All @@ -7,6 +9,10 @@ def __download(core, filepath, request):
core.shutil.copyfileobj(r.raw, f)

def __extract_gzip(core, archivepath, filename):
exts = subtitles_exts + subtitles_exts_secondary
if not any(filename.lower().endswith(ext) for ext in exts):
filename = filename + ".srt"

filepath = core.os.path.join(core.utils.temp_dir, filename)

if core.utils.py2:
Expand All @@ -25,8 +31,8 @@ def __extract_gzip(core, archivepath, filename):
return filepath

def __extract_zip(core, archivepath, filename, episodeid):
sub_exts = ['.srt', '.sub']
sub_exts_secondary = ['.smi', '.ssa', '.aqt', '.jss', '.ass', '.rt', '.txt']
sub_exts = subtitles_exts
sub_exts_secondary = subtitles_exts_secondary

try:
using_libvfs = False
Expand All @@ -39,9 +45,13 @@ def __extract_zip(core, archivepath, filename, episodeid):
(dirs, files) = core.kodi.xbmcvfs.listdir('archive://%s' % archivepath_)
namelist = [file.decode(core.utils.default_encoding) if core.utils.py2 else file for file in files]

subfile = core.utils.find_file_in_archive(core, namelist, sub_exts, episodeid)
if not subfile:
subfile = core.utils.find_file_in_archive(core, namelist, sub_exts_secondary, episodeid)
subfile = core.utils.find_file_in_archive(core, namelist, sub_exts + sub_exts_secondary, episodeid)

if subfile:
# Add the subtitle file extension.
subfilename_and_ext = subfile.rsplit(".", 1)
if len(subfilename_and_ext) > 1:
filename = filename + "." + subfilename_and_ext[-1]

dest = core.os.path.join(core.utils.temp_dir, filename)
if not subfile:
Expand All @@ -67,9 +77,8 @@ def __extract_zip(core, archivepath, filename, episodeid):
return dest

def __insert_lang_code_in_filename(core, filename, lang_code):
filename_chunks = core.utils.strip_non_ascii_and_unprintable(filename).split('.')
filename_chunks.insert(-1, lang_code)
return '.'.join(filename_chunks)
name = core.utils.strip_non_ascii_and_unprintable(filename).rsplit(".", 1)[0]
return ".".join([name, lang_code])

def __postprocess(core, filepath, lang_code):
try:
Expand Down Expand Up @@ -111,15 +120,31 @@ def __postprocess(core, filepath, lang_code):
f.write(text.encode(core.utils.default_encoding))
except: pass

def __copy_sub_local(core, subfile):
# Copy the subfile to local.
media_name = core.os.path.splitext(core.os.path.basename(core.kodi.xbmc.getInfoLabel('Player.Filename')))[0]
sub_name, lang_code, extension = core.os.path.basename(subfile).rsplit(".", 2)
file_dest, folder_dest = None, None
if core.kodi.get_kodi_setting("subtitles.storagemode") == 0:
folder_dest = core.kodi.xbmc.getInfoLabel('Player.Folderpath')
file_dest = core.os.path.join(folder_dest, ".".join([media_name, lang_code, extension]))
elif core.kodi.get_kodi_setting("subtitles.storagemode") == 1:
folder_dest = core.kodi.get_kodi_setting("subtitles.custompath")
file_dest = core.os.path.join(folder_dest, ".".join([media_name, lang_code, extension]))

if file_dest and core.kodi.xbmcvfs.exists(folder_dest):
core.kodi.xbmcvfs.copy(subfile, file_dest)

def download(core, params):
core.logger.debug(lambda: core.json.dumps(params, indent=2))

core.shutil.rmtree(core.utils.temp_dir, ignore_errors=True)
core.kodi.xbmcvfs.mkdirs(core.utils.temp_dir)

actions_args = params['action_args']
lang_code = core.utils.get_lang_id(actions_args['lang'], core.kodi.xbmc.ISO_639_2)
lang_code = core.utils.get_lang_id(actions_args['lang'], core.kodi.xbmc.ISO_639_1)
filename = __insert_lang_code_in_filename(core, actions_args['filename'], lang_code)
filename = core.utils.slugify_filename(filename)
archivepath = core.os.path.join(core.utils.temp_dir, 'sub.zip')

service_name = params['service_name']
Expand All @@ -140,6 +165,7 @@ def download(core, params):
__postprocess(core, filepath, lang_code)

if core.api_mode_enabled:
__copy_sub_local(core, filepath)
return filepath

listitem = core.kodi.xbmcgui.ListItem(label=filepath, offscreen=True)
Expand Down
51 changes: 49 additions & 2 deletions a4kSubtitles/lib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ def strip_non_ascii_and_unprintable(text):
result = ''.join(char for char in text if char in string.printable)
return result.encode('ascii', errors='ignore').decode('ascii', errors='ignore')

def slugify_filename(text):
return re.sub(r'[\\/*?:"<>|]', '_', text)

def get_lang_id(language, lang_format):
try:
return get_lang_ids([language], lang_format)[0]
Expand Down Expand Up @@ -173,15 +176,16 @@ def get_json(path, filename):
with open_file_wrapper(json_path)() as json_result:
return json.load(json_result)

def find_file_in_archive(core, namelist, exts, part_of_filename=''):
def find_file_in_archive(core, namelist, exts, episode_number=''):
first_ext_match = None
exact_file = None
for file in namelist:
file_lower = file.lower()
if any(file_lower.endswith(ext) for ext in exts):
sub_meta = extract_season_episode(file_lower, True)
if not first_ext_match:
first_ext_match = file
if (part_of_filename == '' or part_of_filename in file_lower):
if (episode_number == '' or sub_meta.episode == episode_number):
exact_file = file
break

Expand Down Expand Up @@ -212,3 +216,46 @@ def extract_zipfile_member(zipfile, filename, dest):
except:
filename = filename.encode(default_encoding).decode(py3_zip_missing_utf8_flag_fallback_encoding)
return zipfile.extract(filename, dest)

def extract_season_episode(filename, episode_fallback=False):
episode_pattern = r'(?:e|ep.?|episode.?)(\d{1,5})'
season_pattern = r'(?:s|season.?)(\d{1,5})'
combined_pattern = r'\b(?:s|season)(\d{1,5})\s?[x|\-|\_|\s]\s?[a-z]?(\d{1,5})\b'
range_episodes_pattern = r'\b(?:.{1,4}e|ep|eps|episodes|\s)?(\d{1,5}?)(?:v.?)?\s?[\-|\~]\s?(\d{1,5})(?:v.?)?\b'
date_pattern = r'\b(\d{2,4}-\d{1,2}-\d{2,4})\b'

filename = re.sub(date_pattern, "", filename)
season_match = re.search(season_pattern, filename, re.IGNORECASE)
episode_match = re.search(episode_pattern, filename, re.IGNORECASE)
combined_match = re.search(combined_pattern, filename, re.IGNORECASE)
range_episodes_match = re.findall(range_episodes_pattern, filename, re.IGNORECASE)

season = season_match.group(1) if season_match else None
episode = episode_match.group(1) if episode_match else None
episodes_range = range(0)

if combined_match:
season = season if season else combined_match.group(1)
episode = episode if episode else combined_match.group(2)

if range_episodes_match:
range_start, range_end = map(int, range_episodes_match[-1])
episodes_range = range(range_start, range_end)

if episode_fallback and not episode:
# If no matches found, attempt to capture episode-like sequences
fallback_pattern = re.compile(r'\bE?P?(\d{1,5})v?\d?\b', re.IGNORECASE)
filename = re.sub(r'[\s\.\:\;\(\)\[\]\{\}\\\/\&\€\'\`\#\@\=\$\?\!\%\+\-\_\*\^]', " ", filename)
fallback_matches = fallback_pattern.findall(filename)

if fallback_matches:
# Assuming the last number in the fallback matches is the episode number
episode = fallback_matches[-1].zfill(3)

return DictAsObject(
{
"season": season.zfill(3) if season else None,
"episode": episode.zfill(3) if episode else None,
"episodes_range": episodes_range
}
)
3 changes: 2 additions & 1 deletion a4kSubtitles/lib/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ def __update_info_from_imdb(core, meta, pagination_token=''):
meta.episode = str(result['series']['episodeNumber']['episodeNumber'])
else:
meta.tvshow = result['titleText']['text']
meta.tvshow_year = str(result['releaseDate']['year'])
if meta.tvshow_year == '':
meta.tvshow_year = str(result['releaseDate']['year'])

episodes = result['episodes']['result']['edges']
s_number = int(meta.season)
Expand Down
82 changes: 65 additions & 17 deletions a4kSubtitles/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ def __query_service(core, service_name, meta, request, results):
core.progress_text = core.progress_text.replace(service.display_name, '')
core.kodi.update_progress(core)

def __add_results(core, results): # pragma: no cover
def __add_results(core, results, meta): # pragma: no cover
for item in results:
listitem = core.kodi.create_listitem(item)

item['action_args'].setdefault("episodeid", meta.episode.zfill(3) if meta.episode else "")
action_args = core.utils.quote_plus(core.json.dumps(item['action_args']))

core.kodi.xbmcplugin.addDirectoryItem(
Expand Down Expand Up @@ -179,17 +180,17 @@ def __prepare_results(core, meta, results):

extra = ['extended', 'cut', 'remastered', 'proper']

filename = core.utils.unquote(meta.filename).lower()
filename = core.utils.unquote(meta.filename_without_ext).lower()
regexsplitwords = r'[\s\.\:\;\(\)\[\]\{\}\\\/\&\€\'\`\#\@\=\$\?\!\%\+\-\_\*\^]'
nameparts = core.re.split(regexsplitwords, filename)
meta_nameparts = core.re.split(regexsplitwords, filename)

release_list = [i for i in nameparts if i in release]
quality_list = [i for i in nameparts if i in quality]
service_list = [i for i in nameparts if i in service]
codec_list = [i for i in nameparts if i in codec]
audio_list = [i for i in nameparts if i in audio]
color_list = [i for i in nameparts if i in color]
extra_list = [i for i in nameparts if i in extra]
release_list = [i for i in meta_nameparts if i in release]
quality_list = [i for i in meta_nameparts if i in quality]
service_list = [i for i in meta_nameparts if i in service]
codec_list = [i for i in meta_nameparts if i in codec]
audio_list = [i for i in meta_nameparts if i in audio]
color_list = [i for i in meta_nameparts if i in color]
extra_list = [i for i in meta_nameparts if i in extra]

for item in release_list:
for group in release_groups:
Expand Down Expand Up @@ -227,22 +228,69 @@ def __prepare_results(core, meta, results):
color_list = group
break

def _filter_name(x):
name_diff_ignore = quality + codec + audio + color
name_diff_ignore += ["multi", 'multiple', 'sub', 'subs', 'subtitle']

if x.isdigit():
x = str(int(x)).zfill(3)
elif x.lower() in name_diff_ignore:
x = ''
return x.lower()

def _match_numbers(a, b):
offset = 0
for s in b:
s = core.re.sub(r'v[1-4]', "", s)
if not s.isdigit():
continue
elif meta.episode and s.zfill(3) == meta.episode.zfill(3):
offset += 0.4
elif s in a:
offset += 0.2

return offset

def sorter(x):
name = x['name'].lower()
nameparts = core.re.split(regexsplitwords, name)

cleaned_nameparts = list(filter(len, map(_filter_name, nameparts)))
cleaned_file_nameparts = list(filter(len, map(_filter_name, meta_nameparts)))
matching_offset = 0

if meta.is_tvshow:
sub_meta = core.utils.extract_season_episode(name)

is_season = sub_meta.season and sub_meta.season == meta.season.zfill(3)
is_episode = sub_meta.episode and sub_meta.episode == meta.episode.zfill(3)

# Handle the parsed season and episode.
if is_season and not sub_meta.season:
matching_offset += 0.6
if is_season and is_episode:
matching_offset += 0.4
elif meta.episode and int(meta.episode) in sub_meta.episodes_range:
matching_offset += 0.3
elif sub_meta.season and sub_meta.episode:
matching_offset -= 0.5

if matching_offset == 0:
matching_offset = _match_numbers(cleaned_file_nameparts, cleaned_nameparts)

return (
not x['lang'] == meta.preferredlanguage,
meta.languages.index(x['lang']),
not x['sync'] == 'true',
-sum(i in nameparts for i in quality_list) * 10,
-(core.difflib.SequenceMatcher(None, cleaned_file_nameparts, cleaned_nameparts).ratio() + matching_offset),
-sum(i in nameparts for i in release_list) * 10,
-sum(i in nameparts for i in quality_list) * 10,
-sum(i in nameparts for i in codec_list) * 10,
-sum(i in nameparts for i in service_list) * 10,
-sum(i in nameparts for i in audio_list),
-sum(i in nameparts for i in color_list),
-sum(i in nameparts for i in extra_list),
-core.difflib.SequenceMatcher(None, name, filename).ratio(),
-core.difflib.SequenceMatcher(None, filename, name).ratio(),
-x['rating'],
not x['impaired'] == 'true',
x['service'],
Expand Down Expand Up @@ -275,11 +323,11 @@ def __wait_threads(core, request_threads):

core.utils.wait_threads(threads)

def __complete_search(core, results):
def __complete_search(core, results, meta):
if core.api_mode_enabled:
return results

__add_results(core, results) # pragma: no cover
__add_results(core, results, meta) # pragma: no cover

def __search(core, service_name, meta, results):
service = core.services[service_name]
Expand Down Expand Up @@ -326,7 +374,7 @@ def search(core, params):
threads.append((auth_thread, search_thread))

if len(threads) == 0:
return __complete_search(core, results)
return __complete_search(core, results, meta)

core.progress_text = core.progress_text[:-1]
core.kodi.update_progress(core)
Expand All @@ -344,7 +392,7 @@ def check_cancellation(): # pragma: no cover

cancellation_token.iscanceled = True
final_results = __prepare_results(core, meta, results)
ready_queue.put(__complete_search(core, final_results))
ready_queue.put(__complete_search(core, final_results, meta))
break

def wait_all_results():
Expand All @@ -353,7 +401,7 @@ def wait_all_results():
return
final_results = __prepare_results(core, meta, results)
__save_results(core, meta, final_results)
ready_queue.put(__complete_search(core, final_results))
ready_queue.put(__complete_search(core, final_results, meta))

core.threading.Thread(target=check_cancellation).start()
core.threading.Thread(target=wait_all_results).start()
Expand Down
9 changes: 9 additions & 0 deletions a4kSubtitles/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ def start(api):
core = api.core
monitor = core.kodi.xbmc.Monitor()
has_done_subs_check = False
prev_playing_filename = ''

while not monitor.abortRequested():
if monitor.waitForAbort(1):
Expand All @@ -15,10 +16,18 @@ def start(api):
has_video = (core.kodi.xbmc.getCondVisibility('VideoPlayer.Content(movies)')
or core.kodi.xbmc.getCondVisibility('VideoPlayer.Content(episodes)'))
if not has_video and has_done_subs_check:
playing_filename = ''
has_done_subs_check = False

has_video_duration = core.kodi.xbmc.getCondVisibility('Player.HasDuration')

# In-case episode changed.
if has_video:
playing_filename = core.kodi.xbmc.getInfoLabel('Player.Filename')
if prev_playing_filename != playing_filename:
has_done_subs_check = False
prev_playing_filename = playing_filename

if not has_video or not has_video_duration or has_done_subs_check:
continue

Expand Down
2 changes: 1 addition & 1 deletion a4kSubtitles/services/podnadpisi.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def map_result(result):
'action_args': {
'url': '%s%s' % (__url, result['download']),
'lang': lang,
'filename': name
'filename': name,
}
}

Expand Down
Loading

0 comments on commit de790ee

Please sign in to comment.