Switch to python3
This commit is contained in:
parent
531041e89a
commit
9ba4b6a91a
5286 changed files with 677347 additions and 576888 deletions
486
Shared/lib/python3.4/site-packages/ox/movie.py
Normal file
486
Shared/lib/python3.4/site-packages/ox/movie.py
Normal file
|
|
@ -0,0 +1,486 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
# GPL 2012
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
from .normalize import normalize_name
|
||||
from .text import get_sort_name, find_re
|
||||
from .file import EXTENSIONS
|
||||
|
||||
__all__ = ['parse_movie_path', 'create_movie_path', 'get_oxid']
|
||||
|
||||
LANGUAGES = ['en', 'fr', 'de', 'es', 'it']
|
||||
|
||||
'''
|
||||
Naming scheme:
|
||||
X/[Group, The; Lastname, Firstname/]The Title[ (YEAR[-[YEAR]])]/
|
||||
The Title[ ([SXX][EYY[+ZZ|-ZZ]])[ Episode Title]][.Version][.Part XY[.Part Title][.en][.fr].xyz
|
||||
'''
|
||||
|
||||
def format_path(data, directory_key='director'):
|
||||
def format_underscores(string):
|
||||
return re.sub('^\.|\.$|:|/|\?|<|>', '_', string)
|
||||
director = data['directorSort'] or ['Unknown Director']
|
||||
title = data['seriesTitle' if data['isEpisode'] else 'title'] or 'Untitled'
|
||||
year = data['seriesYear' if data['isEpisode'] else 'year'] or None
|
||||
parts = map(format_underscores, filter(lambda x: x != None, [
|
||||
u'; '.join(director[:10]),
|
||||
u'%s%s' % (title, u' (%s)' % year if year else ''),
|
||||
u'%s%s%s%s%s%s' % (
|
||||
data['title'] or 'Untitled',
|
||||
u'.%s' % data['version'] if data['version'] else '',
|
||||
u'.Part %s' % data['part'] if data['part'] else '',
|
||||
u'.%s' % data['partTitle'] if data['partTitle'] else '',
|
||||
u'.%s' % data['language'] if data['language'] else '',
|
||||
u'.%s' % data['extension'] if data['extension'] else ''
|
||||
)
|
||||
]))
|
||||
if data.get('subdirectory'):
|
||||
parts.insert(-1, data['subdirectory'])
|
||||
return unicodedata.normalize('NFD', u'/'.join(parts))
|
||||
|
||||
def parse_item_files(files):
|
||||
# parses a list of file objects associated with one item (file objects
|
||||
# as returned by parse_path, but extended with 'path' and 'time')
|
||||
# and returns a list of version objects (in case of english-only subtitles,
|
||||
# version[i]['files'][j]['normalizedPath'] will be modified)
|
||||
def get_file_key(file):
|
||||
return '\n'.join([
|
||||
file['version'] or '',
|
||||
file['part'] or '',
|
||||
file['language'] or '',
|
||||
file['extension'] or ''
|
||||
])
|
||||
def get_version_key(file, extension=True):
|
||||
return '%s/%s-part/%s' % (
|
||||
file['version'] or '',
|
||||
'single' if file['part'] == None else 'multi',
|
||||
file['extension'] if extension else ''
|
||||
)
|
||||
# filter out duplicate files (keep shortest path, sorted alphabetically)
|
||||
# since same version+part+language+extension can still differ in part title,
|
||||
# ''/'en' or 'mpg'/'mpeg', or have an unparsed section in their path
|
||||
unique_files = []
|
||||
duplicate_files = []
|
||||
for key in [get_file_key(file) for file in files]:
|
||||
key_files = sorted(
|
||||
sorted([file for file in files if get_file_key(file) == key]),
|
||||
key=lambda x: len(x['path'])
|
||||
)
|
||||
unique_files.append(key_files[0])
|
||||
duplicate_files += key_files[1:]
|
||||
# determine versions ('version.single|multi-part.videoextension')
|
||||
version_files = {}
|
||||
time = {}
|
||||
video_files = [file for file in unique_files if file['type'] == 'video']
|
||||
versions = set([file['version'] for file in video_files])
|
||||
for version in versions:
|
||||
for file in [file for file in video_files if file['version'] == version]:
|
||||
version_key = get_version_key(file)
|
||||
version_files[version_key] = (version_files[version_key] if version_key in version_files else []) + [file]
|
||||
time[version_key] = sorted([time[version_key], file['time']])[-1] if version_key in time else file['time']
|
||||
# determine preferred video extension (newest)
|
||||
extension = {}
|
||||
for key in set(['/'.join(version_key.split('/')[:-1]) + '/' for version_key in version_files]):
|
||||
extensions = set([version_key.split('/')[-1] for version_key in version_files if version_key.startswith(key)])
|
||||
extension[key] = sorted(extensions, key=lambda x: time[key + x])[-1]
|
||||
# associate other (non-video) files
|
||||
other_files = [file for file in unique_files if file['type'] != 'video']
|
||||
versions = set([file['version'] for file in other_files])
|
||||
for version in versions:
|
||||
for file in [file for file in other_files if file['version'] == version]:
|
||||
key = get_version_key(file, extension=False)
|
||||
if key in extension:
|
||||
version_files[key + extension[key]].append(file)
|
||||
else:
|
||||
version_files[key] = (version_files[key] if key in version_files else []) + [file]
|
||||
extension[key] = ''
|
||||
# determine main files (video + srt)
|
||||
full = {}
|
||||
language = {}
|
||||
main_files = {}
|
||||
for version_key in version_files:
|
||||
parts = sorted(list(set([file['part'] for file in version_files[version_key]])))
|
||||
# determine if all parts have one video file
|
||||
video_files = [file for file in version_files[version_key] if file['type'] == 'video']
|
||||
full[version_key] = len(video_files) == len(parts)
|
||||
main_files[version_key] = video_files if full[version_key] else []
|
||||
# determine preferred subtitle language
|
||||
language[version_key] = None
|
||||
subtitle_files = [file for file in version_files[version_key] if file['extension'] == 'srt']
|
||||
for subtitle_language in sorted(
|
||||
list(set([file['language'] for file in subtitle_files])),
|
||||
key=lambda x: LANGUAGES.index(x) if x in LANGUAGES else x
|
||||
):
|
||||
language_files = [file for file in subtitle_files if file['language'] == subtitle_language]
|
||||
if len(subtitle_files) == len(parts):
|
||||
language[version_key] = subtitle_language
|
||||
main_files[version_key] += language_files
|
||||
break
|
||||
# determine main version (best srt language, then video time)
|
||||
main_version = None
|
||||
full_version_keys = sorted(
|
||||
[version_key for version_key in version_files if full[version_key]],
|
||||
key=lambda x: time[x],
|
||||
reverse=True
|
||||
)
|
||||
if full_version_keys:
|
||||
language_version_keys = sorted(
|
||||
[version_key for version_key in full_version_keys if language[version_key]],
|
||||
key=lambda x: LANGUAGES.index(language[x]) if language[x] in LANGUAGES else language[x]
|
||||
)
|
||||
main_version = language_version_keys[0] if language_version_keys else full_version_keys[0]
|
||||
# add duplicate files
|
||||
for file in duplicate_files:
|
||||
key = get_version_key(file, extension=False)
|
||||
version_key = '%s%s' % (key, extension[key] if key in extension else '')
|
||||
version_files[version_key] = (version_files[version_key] if version_key in version_files else []) + [file]
|
||||
# remove unneeded '.en'
|
||||
for version_key in version_files:
|
||||
for extension in EXTENSIONS['subtitle']:
|
||||
subtitle_files = [file for file in version_files[version_key] if file['extension'] == extension]
|
||||
subtitle_languages = list(set([file['language'] for file in subtitle_files]))
|
||||
if len(subtitle_languages) == 1 and subtitle_languages[0] == LANGUAGES[0]:
|
||||
for subtitle_file in subtitle_files:
|
||||
subtitle_file['normalizedPath'] = format_path(dict(subtitle_file, **{'language': None}))
|
||||
# return data
|
||||
data = []
|
||||
for version_key in version_files:
|
||||
data.append({
|
||||
'files': sorted(
|
||||
[dict(file, isMainFile=file in main_files[version_key]) for file in version_files[version_key]],
|
||||
key=lambda x: x['path']
|
||||
),
|
||||
'isFullVersion': full[version_key],
|
||||
'isMainVersion': version_key == main_version,
|
||||
'subtitleLanguage': language[version_key] if version_key in language else None,
|
||||
'version': version_key
|
||||
})
|
||||
return data
|
||||
|
||||
def parse_path(path, directory_key='director'):
|
||||
'''
|
||||
# all keys
|
||||
>>> parse_path('Frost, Mark; Lynch, David/Twin Peaks (1991)/Twin Peaks (S01E01) Pilot.European Version.Part 1.Welcome to Twin Peaks.en.fr.MPEG')['normalizedPath']
|
||||
'Frost, Mark; Lynch, David/Twin Peaks (1991)/Twin Peaks (S01E00) Pilot.European Version.Part 1.Welcome to Twin Peaks.en.fr.mpg'
|
||||
|
||||
# pop directory title off file name
|
||||
>>> parse_path("Unknown Director/www.xxx.com.._/www.xxx.com....Director's Cut.avi")['version']
|
||||
"Director's Cut"
|
||||
|
||||
# handle dots
|
||||
>>> parse_path("Unknown Director/Unknown Title (2000)/... Mr. .com....Director's Cut.srt")['version']
|
||||
"Director's Cut"
|
||||
|
||||
# multiple years, season zero, multiple episodes, dots in episode title and part title
|
||||
>>> parse_path('Groening, Matt/The Simpsons (1989-2012)/The Simpsons (S00E01-02) D.I.Y..Uncensored Version.Part 1.D.I.Y..de.avi')['normalizedPath']
|
||||
'Groening, Matt/The Simpsons (1989-2012)/The Simpsons (S01E01+02) D.I.Y..Uncensored Version.Part 1.D.I.Y..de.avi'
|
||||
|
||||
# handle underscores
|
||||
>>> parse_path('Unknown Director/_com_ 1_0 _ NaN.._/_com_ 1_0 _ NaN....avi')['title']
|
||||
'.com: 1/0 / NaN...'
|
||||
|
||||
# TODO: '.com.avi'
|
||||
'''
|
||||
def parse_title(string):
|
||||
return title, year
|
||||
def parse_type(string):
|
||||
for type in EXTENSIONS:
|
||||
if string in EXTENSIONS[type]:
|
||||
return type
|
||||
return None
|
||||
def parse_underscores(string):
|
||||
# '^_' or '_$' is '.'
|
||||
string = re.sub('^_', '.', string)
|
||||
string = re.sub('_$', '.', string)
|
||||
# '_.foo$' or '_ (' is '?'
|
||||
string = re.sub('_(?=(\.\w+$| \())', '?', string)
|
||||
# ' _..._ ' is '<...>'
|
||||
string = re.sub('(?<= )_(.+)_(?= )', '<\g<1>>', string)
|
||||
# 'foo_bar' or 'foo _ bar' is '/'
|
||||
string = re.sub('(?<=\w)_(?=\w)', '/', string)
|
||||
string = re.sub(' _ ', ' / ', string)
|
||||
# 'foo_ ' is ':'
|
||||
string = re.sub('(?<=\w)_ ', ': ', string)
|
||||
return string
|
||||
data = {}
|
||||
parts = map(lambda x: parse_underscores(x.strip()), path.split('/'))
|
||||
# subdirectory
|
||||
if len(parts) > 4:
|
||||
data['subdirectory'] = '/'.join(parts[3:-1])
|
||||
parts = parts[:3] + parts[-1:]
|
||||
else:
|
||||
data['subdirectory'] = None
|
||||
length = len(parts)
|
||||
director, title, file = [
|
||||
parts[-3] if length > 2 else None,
|
||||
parts[-2] if length > 1 else None,
|
||||
parts[-1]
|
||||
]
|
||||
# directorSort, director
|
||||
data['directorSort'] = data['director'] = []
|
||||
if director:
|
||||
data['directorSort'] = filter(
|
||||
lambda x: x != 'Unknown Director',
|
||||
director.split('; ')
|
||||
)
|
||||
data['director'] = map(
|
||||
lambda x: ' '.join(reversed(x.split(', '))),
|
||||
data['directorSort']
|
||||
)
|
||||
# title, year
|
||||
data['title'] = data['year'] = None
|
||||
if title:
|
||||
match = re.search(' \(\d{4}(-(\d{4})?)?\)$', title)
|
||||
data['title'] = title[:-len(match.group(0))] if match else title
|
||||
data['year'] = match.group(0)[2:-1] if match else None
|
||||
file_title = re.sub('[/:]', '_', data['title'])
|
||||
# (remove title from beginning of filename if the rest contains a dot)
|
||||
file = re.sub('^' + re.escape(file_title) + '(?=.*\.)', '', file)
|
||||
# (split by nospace+dot+word, but remove spaces preceding extension)
|
||||
parts = re.split('(?<!\s)\.(?=\w)', re.sub('\s+(?=.\w+$)', '', file))
|
||||
title, parts, extension = [
|
||||
parts[0],
|
||||
parts[1:-1],
|
||||
parts[-1] if len(parts) > 1 else None
|
||||
]
|
||||
if not data['title'] and title:
|
||||
data['title'] = title
|
||||
# season, episode, episodes, episodeTitle
|
||||
data['season'] = data['episode'] = data['episodeTitle'] = None
|
||||
data['episodes'] = []
|
||||
match = re.search(' \((S\d{2})?(E\d{2}([+-]\d{2})?)?\)(.+)?', title)
|
||||
if match:
|
||||
if match.group(1):
|
||||
data['season'] = int(match.group(1)[1:])
|
||||
if match.group(2):
|
||||
if len(match.group(2)) == 3:
|
||||
data['episode'] = int(match.group(2)[1:])
|
||||
else:
|
||||
data['episodes'] = range(int(match.group(2)[1:3]), int(match.group(2)[-2:]) + 1)
|
||||
if match.group(4):
|
||||
data['episodeTitle'] = match.group(4)[1:]
|
||||
while data['episodeTitle'] and len(parts) and re.search('^\w+\.*$', parts[0]) and not re.search('^[a-z]{2}$', parts[0]):
|
||||
data['episodeTitle'] += '.%s' % parts.pop(0)
|
||||
# isEpisode, seriesTitle, seriesYear
|
||||
data['isEpisode'] = False
|
||||
data['seriesTitle'] = data['seriesYear'] = None
|
||||
if data['season'] != None or data['episode'] != None or data['episodes']:
|
||||
data['isEpisode'] = True
|
||||
data['seriesTitle'] = data['title']
|
||||
season = 'S%02d' % data['season'] if data['season'] != None else ''
|
||||
episode = ''
|
||||
if data['episode'] != None:
|
||||
episode = 'E%02d' % data['episode']
|
||||
elif data['episodes']:
|
||||
episode = 'E%02d%s%02d' % (
|
||||
data['episodes'][0], '+' if len(data['episodes']) == 2 else '-', data['episodes'][-1]
|
||||
)
|
||||
episodeTitle = ' %s' % data['episodeTitle'] if data['episodeTitle'] else ''
|
||||
data['title'] += ' (%s%s)%s' % (season, episode, episodeTitle)
|
||||
data['seriesYear'] = data['year']
|
||||
data['year'] = None
|
||||
# version
|
||||
data['version'] = parts.pop(0) if len(parts) and re.search('^[A-Z0-9]', parts[0]) and not re.search('^Part .', parts[0]) else None
|
||||
# part
|
||||
data['part'] = parts.pop(0)[5:] if len(parts) and re.search('^Part .', parts[0]) else None
|
||||
# partTitle
|
||||
data['partTitle'] = parts.pop(0) if len(parts) and re.search('^[A-Z0-9]', parts[0]) and data['part'] else None
|
||||
while data['partTitle'] and len(parts) and not re.search('^[a-z]{2}$', parts[0]):
|
||||
data['partTitle'] += '.%s' % parts.pop(0)
|
||||
# language
|
||||
data['language'] = parts.pop(0) if len(parts) and re.search('^[a-z]{2}$', parts[0]) else None
|
||||
# extension
|
||||
data['extension'] = re.sub('^mpeg$', 'mpg', extension.lower()) if extension else None
|
||||
# type
|
||||
data['type'] = parse_type(data['extension'])
|
||||
# normalizedPath
|
||||
data['normalizedPath'] = format_path(data)
|
||||
return data
|
||||
|
||||
|
||||
def parse_movie_path(path):
|
||||
"""
|
||||
"A/Abrams, J.J.; Lieber, Jeffrey; Lindelof, Damon/Lost (2004)/Lost.Season 3.Episode 21.Greatest Hits.avi"
|
||||
"B/Balada, Ivan/Metrum (1967)/Metrum.Part 1.en.srt"
|
||||
"N/Nakata, Hideo/L - Change the World (2008)/L - Change the World.Part 2.srt"
|
||||
"R/Reitz, Edgar/Heimat (1984-2006)/Heimat.Season 2.Episode 8.The Wedding.Part 2.avi"
|
||||
"F/Feuillade, Louis/Les vampires (1915)/Les vampires.Episode 10.Part 2.avi"
|
||||
title: 'Les vampires', year: '1915', episode: 10, part: 2
|
||||
|
||||
"G/Godard, Jean-Luc/Histoire(s) du cinema_ Toutes les histoires (1988)/Histoire(s) du cinema_ Toutes les histoires.avi"
|
||||
"G/Godard, Jean-Luc/Six fois deux (1976)/Six fois deux.Part 1A.Y a personne.avi"
|
||||
"G/Godard, Jean-Luc; Miéville, Anne-Marie/France_tour_detour_deux_enfants (1977)/France_tour_detour_deux_enfants.Part 5.Impression_Dictée.avi"
|
||||
|
||||
"L/Labarthe, André S_/Cinéastes de notre temps (1964-)/Cinéastes de notre temps.Episode.Jean Renoir le patron, première partie_ La Recherche du relatif.avi"
|
||||
"S/Scott, Ridley/Blade Runner (1982)/Blade Runner.Directors's Cut.avi"
|
||||
|
||||
or
|
||||
|
||||
T/Title (Year)/Title.avi
|
||||
"""
|
||||
episodeTitle = episodeYear = seriesTitle = None
|
||||
episodeDirector = []
|
||||
parts = path.split('/')
|
||||
|
||||
#title/year
|
||||
if len(parts) == 4:
|
||||
title = parts[2]
|
||||
elif len(parts) > 1:
|
||||
title = parts[1]
|
||||
else:
|
||||
title = parts[0]
|
||||
title = title.replace('_ ', ': ')
|
||||
if title.endswith('_'):
|
||||
title = title[:-1] + '.'
|
||||
if title.startswith('_'):
|
||||
title = '.' + title[1:]
|
||||
|
||||
year = find_re(title, '(\(\d{4}\))')
|
||||
if not year:
|
||||
year = find_re(title, '(\(\d{4}-\d*\))')
|
||||
if year and title.endswith(year):
|
||||
title = title[:-len(year)].strip()
|
||||
year = year[1:-1]
|
||||
if '-' in year:
|
||||
year = find_re(year, '\d{4}')
|
||||
|
||||
#director
|
||||
if len(parts) == 4:
|
||||
director = parts[1]
|
||||
if director.endswith('_'):
|
||||
director = "%s." % director[:-1]
|
||||
director = director.split('; ')
|
||||
director = [normalize_name(d).strip() for d in director]
|
||||
director = filter(lambda d: d not in ('Unknown Director', 'Various Directors'), director)
|
||||
else:
|
||||
director = []
|
||||
|
||||
#extension/language
|
||||
fileparts = [x.replace('||', '. ') for x in parts[-1].replace('. ', '||').split('.')]
|
||||
extension = len(fileparts) > 1 and fileparts[-1] or ''
|
||||
|
||||
if len(fileparts) > 1 and len(fileparts[-2]) == 2:
|
||||
language = fileparts[-2]
|
||||
else:
|
||||
language = ''
|
||||
|
||||
#season/episode/episodeTitle
|
||||
match = re.compile('(.+?) \((S(\d+))?(E(\d+))?\)( (.+?))?\.').match(parts[-1])
|
||||
if match:
|
||||
seriesTitle = match.group(1)
|
||||
season = match.group(3)
|
||||
episode = match.group(5)
|
||||
episodeTitle = (match.group(6) or '').strip()
|
||||
if episode != None:
|
||||
episode = int(episode)
|
||||
if season != None:
|
||||
season = int(season)
|
||||
if episode and not season:
|
||||
season = 1
|
||||
else:
|
||||
season = find_re(parts[-1], '\.Season (\d+)\.')
|
||||
if season:
|
||||
season = int(season)
|
||||
else:
|
||||
season = None
|
||||
|
||||
episode = find_re(parts[-1], '\.Episode[s]* ([\d+]+)\.')
|
||||
if episode:
|
||||
episode = episode.split('+')[0]
|
||||
episode = int(episode)
|
||||
else:
|
||||
episode = None
|
||||
|
||||
if episode and 'Episode %d'%episode in fileparts:
|
||||
episodeTitle = fileparts.index('Episode %d' % episode) + 1
|
||||
episodeTitle = fileparts[episodeTitle]
|
||||
if episodeTitle == extension or episodeTitle.startswith('Part'):
|
||||
episodeTitle = None
|
||||
|
||||
if not season and 'Episode' in fileparts:
|
||||
episodeTitle = fileparts.index('Episode') + 1
|
||||
episodeTitle = fileparts[episodeTitle]
|
||||
if episodeTitle == extension or episodeTitle.startswith('Part'):
|
||||
episodeTitle = None
|
||||
else:
|
||||
season = 1
|
||||
|
||||
if season:
|
||||
seriesTitle = title
|
||||
title = u'%s (S%02d)' % (seriesTitle, season)
|
||||
if isinstance(episode, int):
|
||||
title = u'%s (S%02dE%02d)' % (seriesTitle, season, episode)
|
||||
if episodeTitle:
|
||||
title = u'%s %s' % (title, episodeTitle)
|
||||
|
||||
#part
|
||||
part = find_re(parts[-1], '\.Part (\d+)\.')
|
||||
if part:
|
||||
part = int(part)
|
||||
else:
|
||||
part = 0
|
||||
|
||||
return {
|
||||
'director': director,
|
||||
'episodeDirector': episodeDirector,
|
||||
'episode': episode,
|
||||
'episodeTitle': episodeTitle,
|
||||
'episodeYear': episodeYear,
|
||||
'extension': extension,
|
||||
'language': language,
|
||||
'part': part,
|
||||
'season': season,
|
||||
'seriesTitle': seriesTitle,
|
||||
'title': title,
|
||||
'year': year,
|
||||
}
|
||||
|
||||
def create_movie_path(title, director, year,
|
||||
season, episode, episodeTitle, episodeDirector, episodeYear,
|
||||
part, language, extension):
|
||||
'''
|
||||
{
|
||||
title: '', director: [''], year: '',
|
||||
season: int, episode: int, episodeTitle: '', episodeDirector: [''], episodeYear: '',
|
||||
part: int, language: '', extension: '', extra: bool
|
||||
})
|
||||
'''
|
||||
partTitle = None
|
||||
director = '; '.join(map(get_sort_name, director))
|
||||
episodeDirector = '; '.join(map(get_sort_name, episodeDirector))
|
||||
filename = [title]
|
||||
if season:
|
||||
filename += ['Season %d' % season]
|
||||
if episode:
|
||||
filename += ['Episode %d' % episode]
|
||||
if episodeTitle:
|
||||
filename += [episodeTitle]
|
||||
if part:
|
||||
filename += ['Part %s' % part]
|
||||
if partTitle:
|
||||
filename += [partTitle]
|
||||
if extension:
|
||||
filename += [extension]
|
||||
filename = '.'.join(filename)
|
||||
path = os.path.join(director[0], director, '%s (%s)' % (title, year), filename)
|
||||
return path
|
||||
|
||||
def get_oxid(title, director=[], year='',
|
||||
season='', episode='', episode_title='', episode_director=[], episode_year=''):
|
||||
def get_hash(string):
|
||||
return hashlib.sha1(string.encode('utf-8')).hexdigest().upper()
|
||||
director = ', '.join(director)
|
||||
episode_director = ', '.join(episode_director)
|
||||
if not season and not episode and not episode_title:
|
||||
oxid = get_hash(director)[:8] + get_hash('\n'.join([title, str(year)]))[:8]
|
||||
else:
|
||||
oxid = get_hash('\n'.join([director, title, str(year), str(season)]))[:8] + \
|
||||
get_hash('\n'.join([str(episode), episode_director, episode_title, str(episode_year)]))[:8]
|
||||
return u'0x' + oxid
|
||||
Loading…
Add table
Add a link
Reference in a new issue