Как преобразовать длительность API YouTube в секунды?
ради интереса я хочу конвертировать длительность видео из YouTubes ISO 8601
секунд. Чтобы в будущем доказать мое решение, я выбрал очень длинное видео чтобы проверить его против.
API предоставляет это на время -"duration": "P1W2DT6H21M32S"
Я попытался проанализировать эту продолжительность с dateutil
как предложено в stackoverflow.com/questions/969285.
import dateutil.parser
duration = = dateutil.parser.parse('P1W2DT6H21M32S')
это вызывает исключение
TypeError: unsupported operand type(s) for +=: 'NoneType' and 'int'
что я не хватает?
6 ответов
встроенный модуль dateutil Python поддерживает только синтаксический анализ дат ISO 8601, а не длительности ISO 8601. Для этого вы можете использовать библиотеку "isodate" (в pypi at https://pypi.python.org/pypi/isodate -- установить через pip или easy_install). Эта библиотека имеет полную поддержку длительности ISO 8601, преобразуя их в datetime.объекты timedelta. Поэтому, как только вы импортировали библиотеку, это так же просто, как:
dur=isodate.parse_duration('P1W2DT6H21M32S')
print dur.total_seconds()
работает на python 2.7+. Приняла от JavaScript one-liner для Youtube v3 вопрос здесь.
import re
def YTDurationToSeconds(duration):
match = re.match('PT(\d+H)?(\d+M)?(\d+S)?', duration).groups()
hours = _js_parseInt(match[0]) if match[0] else 0
minutes = _js_parseInt(match[1]) if match[1] else 0
seconds = _js_parseInt(match[2]) if match[2] else 0
return hours * 3600 + minutes * 60 + seconds
# js-like parseInt
# https://gist.github.com/douglasmiranda/2174255
def _js_parseInt(string):
return int(''.join([x for x in string if x.isdigit()]))
# example output
YTDurationToSeconds(u'PT15M33S')
# 933
обрабатывает формат продолжительности iso8061 до степени Youtube использует до часов
разве видео не 1 неделя, 2 дня, 6 часов 21 минута 32 секунды?
YouTube показывает его как 222 часов 21 минут 17 секунд; 1 * 7 * 24 + 2 * 24 + 6 = 222. Я не знаю, откуда берется несоответствие 17 секунд против 32 секунд; также может быть ошибка округления.
на мой взгляд, писать парсер для этого не так уж и сложно. К сожалению dateutil
не похоже на разбор интервалов, только datetime точки.
обновление:
Я вижу, что для этого есть пакет, но как пример мощности регулярного выражения, краткости и непонятного синтаксиса, вот парсер для вас:
import re
# see http://en.wikipedia.org/wiki/ISO_8601#Durations
ISO_8601_period_rx = re.compile(
'P' # designates a period
'(?:(?P<years>\d+)Y)?' # years
'(?:(?P<months>\d+)M)?' # months
'(?:(?P<weeks>\d+)W)?' # weeks
'(?:(?P<days>\d+)D)?' # days
'(?:T' # time part must begin with a T
'(?:(?P<hours>\d+)H)?' # hourss
'(?:(?P<minutes>\d+)M)?' # minutes
'(?:(?P<seconds>\d+)S)?' # seconds
')?' # end of time part
)
from pprint import pprint
pprint(ISO_8601_period_rx.match('P1W2DT6H21M32S').groupdict())
# {'days': '2',
# 'hours': '6',
# 'minutes': '21',
# 'months': None,
# 'seconds': '32',
# 'weeks': '1',
# 'years': None}
Я намеренно не вычисляю точное количество секунд из этих данных здесь. Это выглядит тривиально (см. выше), но на самом деле это не так. Например, расстояние в 2 месяца с 1 января составляет 58 дней (30+28) или 59 (30+29), В зависимости от года, а с 1 марта всегда 61 дней. Правильная реализация календаря должна учитывать все это; для расчета длины клипа Youtube он должен быть чрезмерным.
вот мой ответ, который занимает 9000regex решение (спасибо-удивительное мастерство regex!) и заканчивает работу для случая использования YouTube оригинального плаката, т. е. преобразование часов, минут и секунд в секунды. Я использовал .groups()
вместо .groupdict()
, а затем несколько любовно построенных списков.
import re
def yt_time(duration="P1W2DT6H21M32S"):
"""
Converts YouTube duration (ISO 8061)
into Seconds
see http://en.wikipedia.org/wiki/ISO_8601#Durations
"""
ISO_8601 = re.compile(
'P' # designates a period
'(?:(?P<years>\d+)Y)?' # years
'(?:(?P<months>\d+)M)?' # months
'(?:(?P<weeks>\d+)W)?' # weeks
'(?:(?P<days>\d+)D)?' # days
'(?:T' # time part must begin with a T
'(?:(?P<hours>\d+)H)?' # hours
'(?:(?P<minutes>\d+)M)?' # minutes
'(?:(?P<seconds>\d+)S)?' # seconds
')?') # end of time part
# Convert regex matches into a short list of time units
units = list(ISO_8601.match(duration).groups()[-3:])
# Put list in ascending order & remove 'None' types
units = list(reversed([int(x) if x != None else 0 for x in units]))
# Do the maths
return sum([x*60**units.index(x) for x in units])
извините за не размещение выше - все еще новое здесь и недостаточно очков репутации для добавления комментариев.
это работает путем разбора входного символа строки 1 за раз, если символ является числовым, он просто добавляет его (string add, а не математическое add) к текущему анализируемому значению. Если это один из "wdhms", текущее значение присваивается соответствующей переменной (неделя, день, час, минута, секунда), и значение затем сбрасывается готовым принять следующее значение. Наконец, он суммирует количество секунд из 5 проанализированных значений.
def ytDurationToSeconds(duration): #eg P1W2DT6H21M32S
week = 0
day = 0
hour = 0
min = 0
sec = 0
duration = duration.lower()
value = ''
for c in duration:
if c.isdigit():
value += c
continue
elif c == 'p':
pass
elif c == 't':
pass
elif c == 'w':
week = int(value) * 604800
elif c == 'd':
day = int(value) * 86400
elif c == 'h':
hour = int(value) * 3600
elif c == 'm':
min = int(value) * 60
elif c == 's':
sec = int(value)
value = ''
return week + day + hour + min + sec
Так вот что я придумал-пользовательский парсер для интерпретации времени:
def durationToSeconds(duration):
"""
duration - ISO 8601 time format
examples :
'P1W2DT6H21M32S' - 1 week, 2 days, 6 hours, 21 mins, 32 secs,
'PT7M15S' - 7 mins, 15 secs
"""
split = duration.split('T')
period = split[0]
time = split[1]
timeD = {}
# days & weeks
if len(period) > 1:
timeD['days'] = int(period[-2:-1])
if len(period) > 3:
timeD['weeks'] = int(period[:-3].replace('P', ''))
# hours, minutes & seconds
if len(time.split('H')) > 1:
timeD['hours'] = int(time.split('H')[0])
time = time.split('H')[1]
if len(time.split('M')) > 1:
timeD['minutes'] = int(time.split('M')[0])
time = time.split('M')[1]
if len(time.split('S')) > 1:
timeD['seconds'] = int(time.split('S')[0])
# convert to seconds
timeS = timeD.get('weeks', 0) * (7*24*60*60) + \
timeD.get('days', 0) * (24*60*60) + \
timeD.get('hours', 0) * (60*60) + \
timeD.get('minutes', 0) * (60) + \
timeD.get('seconds', 0)
return timeS
теперь это, вероятно, супер не круто и так далее, но это работает, поэтому я делюсь, потому что я забочусь о вас, люди.