22

为了感兴趣,我想将视频时长从 YouTube 转换ISO 8601为秒。为了将来证明我的解决方案,我选择了一个很长的视频来测试它。

API 在其持续时间内提供此功能 -"duration": "P1W2DT6H21M32S"

我尝试按照stackoverflow.com/questions/969285dateutil中的建议解析此持续时间。

import dateutil.parser
duration = = dateutil.parser.parse('P1W2DT6H21M32S')

这会引发异常

TypeError: unsupported operand type(s) for +=: 'NoneType' and 'int'

我错过了什么?

4

8 回答 8

35

Python 的内置 dateutil 模块仅支持解析 ISO 8601 日期,不支持解析 ISO 8601 持续时间。为此,您可以使用“isodate”库(在https://pypi.python.org/pypi/isodate的 pypi 中——通过 pip 或 easy_install 安装)。该库完全支持 ISO 8601 持续时间,将它们转换为 datetime.timedelta 对象。所以一旦你导入了这个库,它就像这样简单:

import isodate
dur = isodate.parse_duration('P1W2DT6H21M32S')
print(dur.total_seconds())
于 2013-05-24T20:48:33.447 回答
9

适用于 python 2.7+。采用JavaScript one-liner for Youtube v3 question here

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 使用时间长达数小时

于 2015-09-09T20:55:15.130 回答
6

这是我的答案,它采用9000的正则表达式解决方案(谢谢 - 对正则表达式的惊人掌握!)并完成了原始海报的 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])

很抱歉没有发布更高的帖子 - 这里仍然是新的,并且没有足够的声誉点来添加评论。

于 2018-04-23T08:43:31.660 回答
4

视频不是1周2天6小时21分32秒吗?

Youtube 显示为 222 小时 21 分 17 秒;1 * 7 * 24 + 2 * 24 + 6 = 222。不过,我不知道 17 秒与 32 秒的差异来自哪里;也可以是舍入误差。

在我看来,为此编写解析器并不难。不幸的是dateutil似乎没有解析间隔,只有日期时间点。

更新:

我看到有一个包可以解决这个问题,但只是作为正则表达式功能、简洁和难以理解的语法的一个例子,这里有一个解析器:

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}

我故意不在这里从这些数据中计算确切的秒数。它看起来微不足道(见上文),但实际上并非如此。例如,从 1 月 1 日起 2 个月的距离为 58 天 (30+28) 或 59 (30+29),具体取决于年份,而从 3 月 1 日起始终为 61 天。适当的日历实施应该考虑到所有这些;对于 Youtube 剪辑长度计算,它必须是过多的。

于 2013-05-24T19:59:19.677 回答
2

这通过一次解析输入字符串 1 个字符来工作,如果字符是数字,它只是将它(字符串添加,而不是数学添加)添加到正在解析的当前值。如果它是“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
于 2016-02-02T17:27:25.847 回答
1

所以这就是我想出的——一个解释时间的自定义解析器:

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

现在它可能是超级不酷等等,但它有效,所以我分享是因为我关心你们。

于 2013-05-24T20:31:52.090 回答
1

扩展9000 的答案,显然 Youtube 的格式是使用几周,而不是几个月,这意味着可以轻松计算总秒数。
这里没有使用命名组,因为我最初需要它来使用 PySpark。

from operator import mul
from itertools import accumulate
import re
from typing import Pattern, List

SECONDS_PER_SECOND: int = 1
SECONDS_PER_MINUTE: int = 60
MINUTES_PER_HOUR: int = 60
HOURS_PER_DAY: int = 24
DAYS_PER_WEEK: int = 7
WEEKS_PER_YEAR: int = 52

ISO8601_PATTERN: Pattern = re.compile(
    r"P(?:(\d+)Y)?(?:(\d+)W)?(?:(\d+)D)?"
    r"T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
)

def extract_total_seconds_from_ISO8601(iso8601_duration: str) -> int:
    """Compute duration in seconds from a Youtube ISO8601 duration format. """
    MULTIPLIERS: List[int] = (
        SECONDS_PER_SECOND, SECONDS_PER_MINUTE, MINUTES_PER_HOUR,
        HOURS_PER_DAY, DAYS_PER_WEEK, WEEKS_PER_YEAR
    )
    groups: List[int] = [int(g) if g is not None else 0 for g in
              ISO8601_PATTERN.match(iso8601_duration).groups()]

    return sum(g * multiplier for g, multiplier in
               zip(reversed(groups), accumulate(MULTIPLIERS, mul)))
于 2019-11-02T12:02:08.733 回答
1

扩展 StanleyZheng 的答案......不需要 _js_parseInt 函数。

import re

def YTDurationToSeconds(duration):
    match = re.match('PT((\d+)H)?((\d+)M)?((\d+)S)?', duration).groups()
    hours = int(match[1]) if match[1] else 0
    minutes = int(match[3]) if match[3] else 0
    seconds = int(match[5]) if match[5] else 0
    return hours * 3600 + minutes * 60 + seconds

# example output 
YTDurationToSeconds('PT15M33S')
# 933
于 2022-01-06T14:11:02.083 回答