Add support for episode names
This commit is contained in:
179
renamer.py
179
renamer.py
@@ -25,6 +25,7 @@ PATTERNS = (
|
||||
r"|noir[-.]edition"
|
||||
r"|black[-.]chrome[-.]edition"
|
||||
r"|extended[-.]edition"
|
||||
r"|hq[-.]edition"
|
||||
r"|theatrical)"),
|
||||
("restrictions", r"(unrated)"),
|
||||
("resolution", r"[0-9]{3,4}[pi]"),
|
||||
@@ -37,6 +38,7 @@ PATTERNS = (
|
||||
("audio", r"%s?(dts(-es)?|ac3|flac|dd5\.1|aac2\.0|dub-line)" % LANGUAGES),
|
||||
("subtitles", r"%s?sub" % LANGUAGES),
|
||||
("language", r"(\d{1,2}x)?%s" % LANGUAGES),
|
||||
("file_extension", r"mkv|avi"),
|
||||
("unknown", r".*")
|
||||
)
|
||||
|
||||
@@ -99,83 +101,130 @@ def process_file(fpath):
|
||||
_lg.warning("%s -> %s", fname, result)
|
||||
|
||||
|
||||
def _get_parsed_title_dict(chunk_list, chunk_map):
|
||||
p_title = collections.defaultdict(list)
|
||||
for idx, chunk in enumerate(chunk_list):
|
||||
chunk_type = chunk_map[idx]
|
||||
p_title[chunk_type].append(chunk)
|
||||
return p_title
|
||||
|
||||
|
||||
def _guess_combined(chunk_values, chunk_map):
|
||||
""" Try to combine unknown chunks in pairs and parse them """
|
||||
is_changed = False
|
||||
p_title = _get_parsed_title_dict(chunk_values, chunk_map)
|
||||
if len(p_title["unknown"]) < 2:
|
||||
return is_changed, chunk_values, chunk_map
|
||||
|
||||
# i - begin of slice, j - end of slice
|
||||
i = 0
|
||||
# process up to second-to-last element
|
||||
while i < len(chunk_map) - 1:
|
||||
# we need slice with at least two elements
|
||||
j = i + 2
|
||||
# we need only unknown elements
|
||||
while set(chunk_map[i:j]) == {"unknown"} and j <= len(chunk_map):
|
||||
# create combined chunk
|
||||
cmb_chunk = ".".join(chunk_values[i:j])
|
||||
cmb_chunk_type = guess_part(cmb_chunk)
|
||||
|
||||
# add new combined chunk in lists
|
||||
# first subelement gets new chunk, rest - None
|
||||
# (will be removed later)
|
||||
if cmb_chunk_type != "unknown":
|
||||
is_changed = True
|
||||
chunk_values[i] = cmb_chunk
|
||||
chunk_map[i] = cmb_chunk_type
|
||||
for idx in range(i+1, j):
|
||||
chunk_values[idx] = None
|
||||
chunk_map[idx] = None
|
||||
# to start checking next chunks right after the end of slice
|
||||
i = idx
|
||||
break
|
||||
# try add more elements to combined chunk
|
||||
else:
|
||||
j += 1
|
||||
|
||||
# start checking next value
|
||||
i += 1
|
||||
|
||||
# clean up from None values
|
||||
chunk_values = list(filter(None, chunk_values))
|
||||
chunk_map = list(filter(None, chunk_map))
|
||||
|
||||
return is_changed, chunk_values, chunk_map
|
||||
|
||||
|
||||
def parse_title(title):
|
||||
""" Split media title to components. """
|
||||
|
||||
chunks = list(filter(None, re.split(SEPARATORS, title)))
|
||||
p_title = collections.defaultdict(list)
|
||||
chunk_values = filter(None, re.split(SEPARATORS, title))
|
||||
|
||||
# remove non-word chunks (like single hyphens)
|
||||
chunks = list(filter(lambda ch: re.search(r"\w+", ch), chunks))
|
||||
chunk_values = list(filter(lambda ch: re.search(r"\w+", ch), chunk_values))
|
||||
|
||||
# parse each chunk
|
||||
unknown_chunks = {}
|
||||
for idx, chunk in enumerate(chunks):
|
||||
pat_type = guess_part(chunk)
|
||||
if pat_type != "unknown":
|
||||
p_title[pat_type].append(chunk)
|
||||
else:
|
||||
unknown_chunks[idx] = chunk
|
||||
chunk_map = []
|
||||
for ch_value in chunk_values:
|
||||
chunk_map.append(guess_part(ch_value))
|
||||
|
||||
# try to combine unknown chunks in pairs and parse them
|
||||
if len(unknown_chunks) > 1:
|
||||
prev_idx = -1
|
||||
for idx in sorted(unknown_chunks.keys()):
|
||||
_, chunk_values, chunk_map = _guess_combined(chunk_values, chunk_map)
|
||||
|
||||
# first unknown chunk, skip
|
||||
if prev_idx < 0:
|
||||
prev_idx = idx
|
||||
# # try to parse unknown chunks, replacing all hyphens in them with dots
|
||||
p_title = _get_parsed_title_dict(chunk_values, chunk_map)
|
||||
is_changed = False
|
||||
if p_title.get("unknown"):
|
||||
spl_ch_values = []
|
||||
spl_ch_map = []
|
||||
for idx, ch_value in enumerate(chunk_values):
|
||||
ch_type = chunk_map[idx]
|
||||
if ch_type == "unknown" and "-" in ch_value:
|
||||
spl_values = ch_value.split("-")
|
||||
for spl_val in spl_values:
|
||||
if not spl_val:
|
||||
continue
|
||||
spl_type = guess_part(spl_val)
|
||||
if spl_type != "unknown":
|
||||
is_changed = True
|
||||
spl_ch_values.append(spl_val)
|
||||
spl_ch_map.append(spl_type)
|
||||
else:
|
||||
spl_ch_values.append(ch_value)
|
||||
spl_ch_map.append(ch_type)
|
||||
|
||||
is_combined, spl_ch_values, spl_ch_map = _guess_combined(spl_ch_values, spl_ch_map)
|
||||
if is_changed or is_combined:
|
||||
chunk_values = spl_ch_values
|
||||
chunk_map = spl_ch_map
|
||||
|
||||
# parse name and episode name
|
||||
# only if there is something except unknown chunks
|
||||
p_title = _get_parsed_title_dict(chunk_values, chunk_map)
|
||||
if len(p_title["unknown"]) != len(chunk_values):
|
||||
idx = 0
|
||||
while idx < len(chunk_map) and chunk_map[idx] == "unknown":
|
||||
chunk_map[idx] = "name"
|
||||
idx += 1
|
||||
# if episode number is found, next unknown chunks are episode name
|
||||
if p_title.get("episode"):
|
||||
idx = chunk_map.index("episode") + 1
|
||||
while idx < len(chunk_map) and chunk_map[idx] == "unknown":
|
||||
chunk_map[idx] = "episode_name"
|
||||
idx += 1
|
||||
|
||||
# at last, strip hyphens from unknown chunks
|
||||
# only if there is something except unknown chunks
|
||||
p_title = _get_parsed_title_dict(chunk_values, chunk_map)
|
||||
if len(p_title["unknown"]) != len(chunk_values):
|
||||
for idx, chunk_type in enumerate(chunk_map):
|
||||
if chunk_type != "unknown":
|
||||
continue
|
||||
# previous unknown chunk does not border with current, skip
|
||||
if (prev_idx + 1) != idx:
|
||||
prev_idx = idx
|
||||
chunk_value = chunk_values[idx]
|
||||
if chunk_value[0] != "-" and chunk_value[-1] != "-":
|
||||
continue
|
||||
chunk_values[idx] = chunk_value.strip("-")
|
||||
|
||||
# create combined chunk
|
||||
cmb_chunk = ".".join([unknown_chunks[prev_idx], unknown_chunks[idx]])
|
||||
cmb_chunk_type = guess_part(cmb_chunk)
|
||||
|
||||
# check next pair if nothing
|
||||
if cmb_chunk_type == "unknown":
|
||||
prev_idx = idx
|
||||
continue
|
||||
|
||||
# if combined chunk matches pattern, add it to found type
|
||||
# and remove from unknown chunks its parts
|
||||
p_title[cmb_chunk_type].append(cmb_chunk)
|
||||
del unknown_chunks[prev_idx]
|
||||
del unknown_chunks[idx]
|
||||
prev_idx = -1
|
||||
|
||||
# try to parse unknown chunks, replacing all hyphens in them with dots
|
||||
if unknown_chunks:
|
||||
# create string from unknown_chunks with dots instead of hyphens
|
||||
u_chunks_str = ".".join(unknown_chunks.values())
|
||||
uc_title = ".".join(filter(None, re.split(SEPARATORS_HYPHEN, u_chunks_str)))
|
||||
# recursion exit condition
|
||||
if uc_title != title:
|
||||
p_uc_title = parse_title(uc_title)
|
||||
# if parsed uc_title has smth else than "unknown", update p_title
|
||||
if list(p_uc_title.keys()) != ["unknown"]:
|
||||
p_title.update(p_uc_title)
|
||||
# unknown_chunks should be cleared,
|
||||
# because it was processed in nested function call
|
||||
unknown_chunks = {}
|
||||
|
||||
# cut name from unknown chunks
|
||||
# name is the first n consequent chunks
|
||||
# only if amount of unknown chunks differs from overall amount of chunks
|
||||
if len(unknown_chunks) != len(chunks):
|
||||
i = 0
|
||||
for idx in sorted(unknown_chunks.keys()):
|
||||
if idx != i:
|
||||
break
|
||||
p_title["name"].append(unknown_chunks[idx])
|
||||
del unknown_chunks[idx]
|
||||
i += 1
|
||||
|
||||
for idx in sorted(unknown_chunks.keys()):
|
||||
p_title["unknown"].append(unknown_chunks[idx])
|
||||
p_title = _get_parsed_title_dict(chunk_values, chunk_map)
|
||||
return dict(p_title)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user