diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e0e7708e..bd84fec3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,14 +23,14 @@ jobs: # You can use PyPy versions in python-version. # For example, pypy2 and pypy3 matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 00000000..70f6c2a5 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,19 @@ +name: Ruff +run-name: Ruff + +on: [ push, pull_request ] + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 + + ruff_format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 + with: + args: format --check diff --git a/m3u8/__init__.py b/m3u8/__init__.py index f0e368d7..d11dbf30 100644 --- a/m3u8/__init__.py +++ b/m3u8/__init__.py @@ -1,10 +1,8 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. import os -import sys from urllib.parse import urljoin, urlsplit from m3u8.httpclient import DefaultHTTPClient @@ -62,7 +60,7 @@ "loads", "load", "parse", - "ParseError" + "ParseError", ) diff --git a/m3u8/mixins.py b/m3u8/mixins.py index 21faf2a0..162b82a1 100644 --- a/m3u8/mixins.py +++ b/m3u8/mixins.py @@ -2,7 +2,7 @@ from urllib.parse import urljoin, urlsplit -class BasePathMixin(object): +class BasePathMixin: @property def absolute_uri(self): if self.uri is None: @@ -31,12 +31,12 @@ def get_path_from_uri(self): def base_path(self, newbase_path): if self.uri is not None: if not self.base_path: - self.uri = "%s/%s" % (newbase_path, self.uri) + self.uri = f"{newbase_path}/{self.uri}" else: self.uri = self.uri.replace(self.base_path, newbase_path) -class GroupedBasePathMixin(object): +class GroupedBasePathMixin: def _set_base_uri(self, new_base_uri): for item in self: item.base_uri = new_base_uri diff --git a/m3u8/model.py b/m3u8/model.py index a2bcef70..c855469f 100644 --- a/m3u8/model.py +++ b/m3u8/model.py @@ -1,9 +1,7 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. import decimal -import errno import os from m3u8.mixins import BasePathMixin, GroupedBasePathMixin @@ -22,7 +20,7 @@ class MalformedPlaylistError(Exception): pass -class M3U8(object): +class M3U8: """ Represents a single M3U8 playlist. Should be instantiated with the content as string. @@ -153,7 +151,7 @@ class M3U8(object): ("allow_cache", "allow_cache"), ("playlist_type", "playlist_type"), ("discontinuity_sequence", "discontinuity_sequence"), - ("is_images_only", "is_images_only") + ("is_images_only", "is_images_only"), ) def __init__( @@ -234,12 +232,12 @@ def _initialize_attributes(self): ) self.image_playlists = PlaylistList() - for img_pl in self.data.get('image_playlists', []): + for img_pl in self.data.get("image_playlists", []): self.image_playlists.append( ImagePlaylist( base_uri=self.base_uri, uri=img_pl["uri"], - image_stream_info=img_pl["image_stream_info"] + image_stream_info=img_pl["image_stream_info"], ) ) @@ -364,7 +362,7 @@ def add_segment(self, segment): def add_rendition_report(self, report): self.rendition_reports.append(report) - def dumps(self): + def dumps(self, timespec="milliseconds"): """ Returns the current m3u8 as a string. You could also use unicode() or str() @@ -376,7 +374,7 @@ def dumps(self): output.append("#EXT-X-MEDIA-SEQUENCE:" + str(self.media_sequence)) if self.discontinuity_sequence: output.append( - "#EXT-X-DISCONTINUITY-SEQUENCE:{}".format(self.discontinuity_sequence) + f"#EXT-X-DISCONTINUITY-SEQUENCE:{self.discontinuity_sequence}" ) if self.allow_cache: output.append("#EXT-X-ALLOW-CACHE:" + self.allow_cache.upper()) @@ -416,7 +414,7 @@ def dumps(self): for key in self.session_keys: output.append(str(key)) - output.append(str(self.segments)) + output.append(self.segments.dumps(timespec)) if self.preload_hint: output.append(str(self.preload_hint)) @@ -680,7 +678,7 @@ def __str__(self): @property def base_path(self): - return super(Segment, self).base_path + return super().base_path @base_path.setter def base_path(self, newbase_path): @@ -702,14 +700,17 @@ def base_uri(self, newbase_uri): class SegmentList(list, GroupedBasePathMixin): - def __str__(self): + def dumps(self, timespec="milliseconds"): output = [] last_segment = None for segment in self: - output.append(segment.dumps(last_segment)) + output.append(segment.dumps(last_segment, timespec)) last_segment = segment return "\n".join(output) + def __str__(self): + return self.dumps() + @property def uri(self): return [seg.uri for seg in self] @@ -995,7 +996,7 @@ def __str__(self): else: media_types += [media.type] media_type = media.type.upper() - stream_inf.append('%s="%s"' % (media_type, media.group_id)) + stream_inf.append(f'{media_type}="{media.group_id}"') return "#EXT-X-STREAM-INF:" + ",".join(stream_inf) + "\n" + self.uri @@ -1087,7 +1088,7 @@ def __str__(self): return "#EXT-X-I-FRAME-STREAM-INF:" + ",".join(iframe_stream_inf) -class StreamInfo(object): +class StreamInfo: bandwidth = None closed_captions = None average_bandwidth = None @@ -1266,7 +1267,7 @@ class SessionDataList(TagList): pass -class Start(object): +class Start: def __init__(self, time_offset, precise=None): self.time_offset = float(time_offset) self.precise = precise @@ -1305,7 +1306,7 @@ def __str__(self): return "\n".join(output) -class ServerControl(object): +class ServerControl: def __init__( self, can_skip_until=None, @@ -1346,7 +1347,7 @@ def __str__(self): return self.dumps() -class Skip(object): +class Skip: def __init__(self, skipped_segments, recently_removed_dateranges=None): self.skipped_segments = skipped_segments self.recently_removed_dateranges = recently_removed_dateranges @@ -1366,7 +1367,7 @@ def __str__(self): return self.dumps() -class PartInformation(object): +class PartInformation: def __init__(self, part_target=None): self.part_target = part_target @@ -1397,7 +1398,7 @@ def dumps(self): for attr in ["byterange_start", "byterange_length"]: if self[attr] is not None: - hint.append("%s=%s" % (denormalize_attribute(attr), self[attr])) + hint.append(f"{denormalize_attribute(attr)}={self[attr]}") return "#EXT-X-PRELOAD-HINT:" + ",".join(hint) @@ -1405,7 +1406,7 @@ def __str__(self): return self.dumps() -class SessionData(object): +class SessionData: def __init__(self, data_id, value=None, uri=None, language=None): self.data_id = data_id self.value = value @@ -1432,7 +1433,7 @@ class DateRangeList(TagList): pass -class DateRange(object): +class DateRange: def __init__(self, **kwargs): self.id = kwargs["id"] self.start_date = kwargs.get("start_date") @@ -1479,7 +1480,7 @@ def dumps(self): # client attributes sorted alphabetically output order is predictable for attr, value in sorted(self.x_client_attrs): - daterange.append("%s=%s" % (denormalize_attribute(attr), value)) + daterange.append(f"{denormalize_attribute(attr)}={value}") return "#EXT-X-DATERANGE:" + ",".join(daterange) @@ -1546,27 +1547,28 @@ def __init__(self, base_uri, uri, image_stream_info): hdcp_level=None, frame_rate=None, pathway_id=image_stream_info.get("pathway_id"), - stable_variant_id=image_stream_info.get("stable_variant_id") + stable_variant_id=image_stream_info.get("stable_variant_id"), ) def __str__(self): image_stream_inf = [] if self.image_stream_info.program_id: - image_stream_inf.append("PROGRAM-ID=%d" % - self.image_stream_info.program_id) + image_stream_inf.append("PROGRAM-ID=%d" % self.image_stream_info.program_id) if self.image_stream_info.bandwidth: - image_stream_inf.append("BANDWIDTH=%d" % - self.image_stream_info.bandwidth) + image_stream_inf.append("BANDWIDTH=%d" % self.image_stream_info.bandwidth) if self.image_stream_info.average_bandwidth: - image_stream_inf.append("AVERAGE-BANDWIDTH=%d" % - self.image_stream_info.average_bandwidth) + image_stream_inf.append( + "AVERAGE-BANDWIDTH=%d" % self.image_stream_info.average_bandwidth + ) if self.image_stream_info.resolution: - res = (str(self.image_stream_info.resolution[0]) + "x" + - str(self.image_stream_info.resolution[1])) + res = ( + str(self.image_stream_info.resolution[0]) + + "x" + + str(self.image_stream_info.resolution[1]) + ) image_stream_inf.append("RESOLUTION=" + res) if self.image_stream_info.codecs: - image_stream_inf.append("CODECS=" + - quoted(self.image_stream_info.codecs)) + image_stream_inf.append("CODECS=" + quoted(self.image_stream_info.codecs)) if self.uri: image_stream_inf.append("URI=" + quoted(self.uri)) if self.image_stream_info.pathway_id: @@ -1580,6 +1582,7 @@ def __str__(self): return "#EXT-X-IMAGE-STREAM-INF:" + ",".join(image_stream_inf) + class Tiles(BasePathMixin): """ Image tiles from a M3U8 playlist @@ -1610,80 +1613,6 @@ def dumps(self): def __str__(self): return self.dumps() -class ImagePlaylist(BasePathMixin): - ''' - ImagePlaylist object representing a link to a - variant M3U8 image playlist with a specific bitrate. - - Attributes: - - `image_stream_info` is a named tuple containing the attributes: - `bandwidth`, `resolution` which is a tuple (w, h) of integers and `codecs`, - - More info: https://github.com/image-media-playlist/spec/blob/master/image_media_playlist_v0_4.pdf - ''' - - def __init__(self, base_uri, uri, image_stream_info): - self.uri = uri - self.base_uri = base_uri - - resolution = image_stream_info.get('resolution') - if resolution is not None: - values = resolution.split('x') - resolution_pair = (int(values[0]), int(values[1])) - else: - resolution_pair = None - - self.image_stream_info = StreamInfo( - bandwidth=image_stream_info.get('bandwidth'), - average_bandwidth=image_stream_info.get('average_bandwidth'), - video=image_stream_info.get('video'), - # Audio, subtitles, closed captions, video range and hdcp level should not exist in - # EXT-X-IMAGE-STREAM-INF, so just hardcode them to None. - audio=None, - subtitles=None, - closed_captions=None, - program_id=image_stream_info.get('program_id'), - resolution=resolution_pair, - codecs=image_stream_info.get('codecs'), - video_range=None, - hdcp_level=None, - frame_rate=None, - pathway_id=image_stream_info.get('pathway_id'), - stable_variant_id=image_stream_info.get('stable_variant_id') - ) - - def __str__(self): - image_stream_inf = [] - if self.image_stream_info.program_id: - image_stream_inf.append('PROGRAM-ID=%d' % - self.image_stream_info.program_id) - if self.image_stream_info.bandwidth: - image_stream_inf.append('BANDWIDTH=%d' % - self.image_stream_info.bandwidth) - if self.image_stream_info.average_bandwidth: - image_stream_inf.append('AVERAGE-BANDWIDTH=%d' % - self.image_stream_info.average_bandwidth) - if self.image_stream_info.resolution: - res = (str(self.image_stream_info.resolution[0]) + 'x' + - str(self.image_stream_info.resolution[1])) - image_stream_inf.append('RESOLUTION=' + res) - if self.image_stream_info.codecs: - image_stream_inf.append('CODECS=' + - quoted(self.image_stream_info.codecs)) - if self.uri: - image_stream_inf.append('URI=' + quoted(self.uri)) - if self.image_stream_info.pathway_id: - image_stream_inf.append( - 'PATHWAY-ID=' + quoted(self.image_stream_info.pathway_id) - ) - if self.image_stream_info.stable_variant_id: - image_stream_inf.append( - 'STABLE-VARIANT-ID=' + quoted(self.image_stream_info.stable_variant_id) - ) - - return '#EXT-X-IMAGE-STREAM-INF:' + ','.join(image_stream_inf) - def find_key(keydata, keylist): if not keydata: diff --git a/m3u8/parser.py b/m3u8/parser.py index 9e717e79..f68d084e 100644 --- a/m3u8/parser.py +++ b/m3u8/parser.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. @@ -6,10 +5,10 @@ import itertools import re from datetime import datetime, timedelta -from urllib.parse import urljoin as _urljoin try: from backports.datetime_fromisoformat import MonkeyPatch + MonkeyPatch.patch_fromisoformat() except ImportError: pass @@ -232,7 +231,7 @@ def parse(content, strict=False, custom_tags_parser=None): _parse_image_stream_inf(line, data) elif line.startswith(protocol.ext_x_images_only): - data['is_images_only'] = True + data["is_images_only"] = True elif line.startswith(protocol.ext_x_tiles): _parse_tiles(line, data, state) @@ -245,11 +244,11 @@ def parse(content, strict=False, custom_tags_parser=None): # blank lines are legal pass - elif (not line.startswith('#')) and (state["expect_segment"]): + elif (not line.startswith("#")) and (state["expect_segment"]): _parse_ts_chunk(line, data, state) state["expect_segment"] = False - elif (not line.startswith('#')) and (state["expect_playlist"]): + elif (not line.startswith("#")) and (state["expect_playlist"]): _parse_variant_playlist(line, data, state) state["expect_playlist"] = False @@ -296,9 +295,7 @@ def _parse_ts_chunk(line, data, state): segment["program_date_time"] = state.pop("program_date_time") if state.get("current_program_date_time"): segment["current_program_date_time"] = state["current_program_date_time"] - state["current_program_date_time"] += timedelta( - seconds=segment["duration"] - ) + state["current_program_date_time"] += timedelta(seconds=segment["duration"]) segment["uri"] = line segment["cue_in"] = state.pop("cue_in", False) segment["cue_out"] = state.pop("cue_out", False) @@ -397,21 +394,18 @@ def _parse_image_stream_inf(line, data): ) image_playlist = { "uri": image_stream_info.pop("uri"), - "image_stream_info": image_stream_info + "image_stream_info": image_stream_info, } data["image_playlists"].append(image_playlist) - def _parse_tiles(line, data, state): attribute_parser = remove_quotes_parser("uri") attribute_parser["resolution"] = str attribute_parser["layout"] = str attribute_parser["duration"] = float - tiles_info = _parse_attribute_list( - protocol.ext_x_tiles, line, attribute_parser - ) + tiles_info = _parse_attribute_list(protocol.ext_x_tiles, line, attribute_parser) data["tiles"].append(tiles_info) @@ -474,6 +468,17 @@ def _parse_cueout_cont(line, state): if len(elements) != 2: return + # EXT-X-CUE-OUT-CONT:2.436/120 style + res = re.match( + r"^[-+]?([0-9]+(\.[0-9]+)?|\.[0-9]+)/[-+]?([0-9]+(\.[0-9]+)?|\.[0-9]+)$", + elements[1] + ) + if res: + state["current_cue_out_elapsedtime"] = res.group(1) + state["current_cue_out_duration"] = res.group(3) + return + + # EXT-X-CUE-OUT-CONT:ElapsedTime=10,Duration=60,SCTE35=... style cue_info = _parse_attribute_list( protocol.ext_x_cue_out_cont, line, @@ -584,9 +589,7 @@ def _parse_part(line, data, state): # this should always be true according to spec if state.get("current_program_date_time"): part["program_date_time"] = state["current_program_date_time"] - state["current_program_date_time"] += timedelta( - seconds=part["duration"] - ) + state["current_program_date_time"] += timedelta(seconds=part["duration"]) part["dateranges"] = state.pop("dateranges", None) part["gap_tag"] = state.pop("gap", None) diff --git a/m3u8/protocol.py b/m3u8/protocol.py index 39300d1e..29c53d93 100644 --- a/m3u8/protocol.py +++ b/m3u8/protocol.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. @@ -41,6 +40,6 @@ ext_x_daterange = "#EXT-X-DATERANGE" ext_x_gap = "#EXT-X-GAP" ext_x_content_steering = "#EXT-X-CONTENT-STEERING" -ext_x_image_stream_inf = '#EXT-X-IMAGE-STREAM-INF' -ext_x_images_only = '#EXT-X-IMAGES-ONLY' -ext_x_tiles = '#EXT-X-TILES' +ext_x_image_stream_inf = "#EXT-X-IMAGE-STREAM-INF" +ext_x_images_only = "#EXT-X-IMAGES-ONLY" +ext_x_tiles = "#EXT-X-TILES" diff --git a/setup.py b/setup.py index f1c01d4a..3a4a9ac4 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ setup( name="m3u8", author="Globo.com", - version="3.6.0", + version="4.1.0", license="MIT", zip_safe=False, include_package_data=True, diff --git a/tests/m3u8server.py b/tests/m3u8server.py index 76de8d19..1d135e7f 100644 --- a/tests/m3u8server.py +++ b/tests/m3u8server.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. @@ -15,7 +14,7 @@ @route("/path/to/redirect_me") -def simple(): +def redirect_route(): redirect("/simple.m3u8") @@ -26,14 +25,14 @@ def simple(): @route("/timeout_simple.m3u8") -def simple(): +def timeout_simple(): time.sleep(5) response.set_header("Content-Type", "application/vnd.apple.mpegurl") return m3u8_file("simple-playlist.m3u8") @route("/path/to/relative-playlist.m3u8") -def simple(): +def relative_playlist(): response.set_header("Content-Type", "application/vnd.apple.mpegurl") return m3u8_file("relative-playlist.m3u8") diff --git a/tests/playlists.py b/tests/playlists.py index 1f565050..32ed41b9 100755 --- a/tests/playlists.py +++ b/tests/playlists.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. @@ -738,6 +737,26 @@ master2500_47234.ts """ +CUE_OUT_CONT_ALT_PLAYLIST = """ +#EXTM3U +#EXT-X-VERSION:3 +#EXT-X-TARGETDURATION:7 +#EXT-X-MEDIA-SEQUENCE:19980226 +#EXT-X-DISCONTINUITY-SEQUENCE:1 +#EXT-X-CUE-OUT:119.987 +#EXTINF:2.000, +segment_19980226.ts +#EXT-X-CUE-OUT-CONT:2/120 +#EXTINF:6.000, +segment_19980227.ts +#EXT-X-CUE-OUT-CONT:8/120.0 +#EXTINF:6.001, +segment_19980228.ts +#EXT-X-CUE-OUT-CONT:14.001/120.0 +#EXTINF:6.001, +segment_19980229.ts +""" + CUE_OUT_ENVIVIO_PLAYLIST = """ #EXTM3U #EXT-X-VERSION:3 @@ -1231,7 +1250,7 @@ #EXT-X-MEDIA:TYPE=AUDIO,NAME="audio-aac-eng",STABLE-RENDITION-ID="a8213e27c12a158ea8660e0fe8bdcac6072ca26d984e7e8603652bc61fdceffa",URI="http://example.com/eng.m3u8" """ -VARIANT_PLAYLIST_WITH_IMAGE_PLAYLISTS = ''' +VARIANT_PLAYLIST_WITH_IMAGE_PLAYLISTS = """ #EXTM3U #EXT-X-VERSION:3 #EXT-X-INDEPENDENT-SEGMENTS @@ -1247,9 +1266,9 @@ index_0_a/new_index_0_a.m3u8S #EXT-X-IMAGE-STREAM-INF:BANDWIDTH=16460,RESOLUTION=320x180,CODECS="jpeg",URI="5x2_320x180/320x180-5x2.m3u8" #EXT-X-IMAGE-STREAM-INF:BANDWIDTH=32920,RESOLUTION=640x360,CODECS="jpeg",URI="5x2_640x360/640x360-5x2.m3u8" -''' +""" -VOD_IMAGE_PLAYLIST = ''' +VOD_IMAGE_PLAYLIST = """ #EXTM3U #EXT-X-VERSION:7 #EXT-X-TARGETDURATION:6 @@ -1296,9 +1315,9 @@ #EXT-X-TILES:RESOLUTION=640x360,LAYOUT=5x2,DURATION=6.006 content-7.jpg #EXT-X-ENDLIST -''' +""" -VOD_IMAGE_PLAYLIST2 = ''' +VOD_IMAGE_PLAYLIST2 = """ #EXTM3U #EXT-X-TARGETDURATION:6 #EXT-X-VERSION:7 @@ -1332,9 +1351,9 @@ #EXT-X-TILES:RESOLUTION=640x360,LAYOUT=4x3,DURATION=2.002 credits_2_1.jpg #EXT-X-ENDLIST -''' +""" -LIVE_IMAGE_PLAYLIST = ''' +LIVE_IMAGE_PLAYLIST = """ #EXTM3U #EXT-X-TARGETDURATION:6 #EXT-X-VERSION:7 @@ -1369,8 +1388,7 @@ content-130.jpg #EXTINF:6.006, content-131.jpg -''' - +""" del abspath, dirname, join diff --git a/tests/test_loader.py b/tests/test_loader.py index 41bcbaa8..509b7714 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,9 +1,9 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. import os +import socket import urllib.parse import m3u8 import pytest @@ -77,28 +77,28 @@ def test_load_should_create_object_from_uri_with_relative_segments(): urlparsed = urllib.parse.urlparse(playlists.RELATIVE_PLAYLIST_URI) base_uri = os.path.normpath(urlparsed.path + "/..") prefix = urlparsed.scheme + "://" + urlparsed.netloc - expected_key_abspath = "%s%skey.bin" % ( + expected_key_abspath = "{}{}key.bin".format( prefix, os.path.normpath(base_uri + "/..") + "/", ) expected_key_path = "../key.bin" - expected_ts1_abspath = "%s%sentire1.ts" % (prefix, "/") + expected_ts1_abspath = "{}{}entire1.ts".format(prefix, "/") expected_ts1_path = "/entire1.ts" - expected_ts2_abspath = "%s%sentire2.ts" % ( + expected_ts2_abspath = "{}{}entire2.ts".format( prefix, os.path.normpath(base_uri + "/..") + "/", ) expected_ts2_path = "../entire2.ts" - expected_ts3_abspath = "%s%sentire3.ts" % ( + expected_ts3_abspath = "{}{}entire3.ts".format( prefix, os.path.normpath(base_uri + "/../.."), ) expected_ts3_path = "../../entire3.ts" - expected_ts4_abspath = "%s%sentire4.ts" % (prefix, base_uri + "/") + expected_ts4_abspath = "{}{}entire4.ts".format(prefix, base_uri + "/") expected_ts4_path = "entire4.ts" - expected_ts5_abspath = "%s%sentire5.ts" % (prefix, base_uri + "/") + expected_ts5_abspath = "{}{}entire5.ts".format(prefix, base_uri + "/") expected_ts5_path = "./entire5.ts" - expected_ts6_abspath = "%s%sentire6.ts" % (prefix, base_uri + "/") + expected_ts6_abspath = "{}{}entire6.ts".format(prefix, base_uri + "/") expected_ts6_path = ".//entire6.ts" assert isinstance(obj, m3u8.M3U8) @@ -141,8 +141,8 @@ def test_presence_of_base_uri_if_provided_when_loading_from_string(): def test_raise_timeout_exception_if_timeout_happens_when_loading_from_uri(): try: - obj = m3u8.load(playlists.TIMEOUT_SIMPLE_PLAYLIST_URI, timeout=1) - except: + m3u8.load(playlists.TIMEOUT_SIMPLE_PLAYLIST_URI, timeout=1) + except socket.timeout: assert True else: assert False diff --git a/tests/test_model.py b/tests/test_model.py index 40c41e30..1eae3d4a 100755 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. @@ -8,7 +7,6 @@ import datetime import os -import sys import playlists import pytest @@ -136,30 +134,30 @@ def test_segment_discontinuity_attribute(): obj = m3u8.M3U8(playlists.DISCONTINUITY_PLAYLIST_WITH_PROGRAM_DATE_TIME) segments = obj.segments - assert segments[0].discontinuity == False - assert segments[5].discontinuity == True - assert segments[6].discontinuity == False + assert segments[0].discontinuity is False + assert segments[5].discontinuity is True + assert segments[6].discontinuity is False def test_segment_cue_out_attribute(): obj = m3u8.M3U8(playlists.CUE_OUT_PLAYLIST) segments = obj.segments - assert segments[1].cue_out == True - assert segments[2].cue_out == True - assert segments[3].cue_out == False + assert segments[1].cue_out is True + assert segments[2].cue_out is True + assert segments[3].cue_out is False def test_segment_cue_out_start_attribute(): obj = m3u8.M3U8(playlists.CUE_OUT_WITH_DURATION_PLAYLIST) - assert obj.segments[0].cue_out_start == True + assert obj.segments[0].cue_out_start is True def test_segment_cue_in_attribute(): obj = m3u8.M3U8(playlists.CUE_OUT_WITH_DURATION_PLAYLIST) - assert obj.segments[2].cue_in == True + assert obj.segments[2].cue_in is True def test_segment_cue_out_cont_dumps(): @@ -220,24 +218,38 @@ def test_segment_cue_out_in_dumps(): def test_segment_elemental_scte35_attribute(): obj = m3u8.M3U8(playlists.CUE_OUT_ELEMENTAL_PLAYLIST) segments = obj.segments - assert segments[4].cue_out == True - assert segments[9].cue_out == False + assert segments[4].cue_out is True + assert segments[9].cue_out is False assert ( segments[4].scte35 == "/DAlAAAAAAAAAP/wFAUAAAABf+//wpiQkv4ARKogAAEBAQAAQ6sodg==" ) +def test_segment_cue_out_cont_alt(): + obj = m3u8.M3U8(playlists.CUE_OUT_CONT_ALT_PLAYLIST) + segments = obj.segments + + assert segments[1].scte35_elapsedtime == '2' + assert segments[1].scte35_duration == '120' + + assert segments[2].scte35_elapsedtime == '8' + assert segments[2].scte35_duration == '120.0' + + assert segments[3].scte35_elapsedtime == '14.001' + assert segments[3].scte35_duration == '120.0' + + def test_segment_envivio_scte35_attribute(): obj = m3u8.M3U8(playlists.CUE_OUT_ENVIVIO_PLAYLIST) segments = obj.segments - assert segments[3].cue_out == True + assert segments[3].cue_out is True assert ( segments[4].scte35 == "/DAlAAAENOOQAP/wFAUBAABrf+//N25XDf4B9p/gAAEBAQAAxKni9A==" ) assert ( segments[5].scte35 == "/DAlAAAENOOQAP/wFAUBAABrf+//N25XDf4B9p/gAAEBAQAAxKni9A==" ) - assert segments[7].cue_out == False + assert segments[7].cue_out is False def test_segment_unknown_scte35_attribute(): @@ -248,8 +260,8 @@ def test_segment_unknown_scte35_attribute(): def test_segment_cue_out_no_duration(): obj = m3u8.M3U8(playlists.CUE_OUT_NO_DURATION_PLAYLIST) - assert obj.segments[0].cue_out_start == True - assert obj.segments[2].cue_in == True + assert obj.segments[0].cue_out_start is True + assert obj.segments[2].cue_in is True def test_segment_asset_metadata_dumps(): @@ -302,7 +314,7 @@ def test_key_attribute_without_initialization_vector(): assert "AES-128" == obj.keys[0].method assert "/key" == obj.keys[0].uri - assert None == obj.keys[0].iv + assert None is obj.keys[0].iv def test_session_keys_on_clear_playlist(): @@ -342,7 +354,7 @@ def test_session_key_attribute_without_initialization_vector(): assert "AES-128" == obj.session_keys[0].method assert "/key" == obj.session_keys[0].uri - assert None == obj.session_keys[0].iv + assert None is obj.session_keys[0].iv def test_segments_attribute(): @@ -372,7 +384,7 @@ def test_segments_attribute_without_title(): assert "/foo/bar-1.ts" == obj.segments[0].uri assert 1500 == obj.segments[0].duration - assert None == obj.segments[0].title + assert None is obj.segments[0].title def test_segments_attribute_without_duration(): @@ -385,7 +397,7 @@ def test_segments_attribute_without_duration(): assert "/foo/bar-1.ts" == obj.segments[0].uri assert "Segment title" == obj.segments[0].title - assert None == obj.segments[0].duration + assert None is obj.segments[0].duration def test_segments_attribute_with_byterange(): @@ -503,34 +515,34 @@ def test_playlists_attribute(): assert "/url/1.m3u8" == obj.playlists[0].uri assert 1 == obj.playlists[0].stream_info.program_id assert 320000 == obj.playlists[0].stream_info.bandwidth - assert None == obj.playlists[0].stream_info.closed_captions - assert None == obj.playlists[0].stream_info.codecs + assert None is obj.playlists[0].stream_info.closed_captions + assert None is obj.playlists[0].stream_info.codecs - assert None == obj.playlists[0].media[0].uri + assert None is obj.playlists[0].media[0].uri assert "high" == obj.playlists[0].media[0].group_id assert "VIDEO" == obj.playlists[0].media[0].type - assert None == obj.playlists[0].media[0].language + assert None is obj.playlists[0].media[0].language assert "High" == obj.playlists[0].media[0].name - assert None == obj.playlists[0].media[0].default - assert None == obj.playlists[0].media[0].autoselect - assert None == obj.playlists[0].media[0].forced - assert None == obj.playlists[0].media[0].characteristics + assert None is obj.playlists[0].media[0].default + assert None is obj.playlists[0].media[0].autoselect + assert None is obj.playlists[0].media[0].forced + assert None is obj.playlists[0].media[0].characteristics assert "/url/2.m3u8" == obj.playlists[1].uri assert 1 == obj.playlists[1].stream_info.program_id assert 120000 == obj.playlists[1].stream_info.bandwidth - assert None == obj.playlists[1].stream_info.closed_captions + assert None is obj.playlists[1].stream_info.closed_captions assert "mp4a.40.5" == obj.playlists[1].stream_info.codecs - assert None == obj.playlists[1].media[0].uri + assert None is obj.playlists[1].media[0].uri assert "low" == obj.playlists[1].media[0].group_id assert "VIDEO" == obj.playlists[1].media[0].type - assert None == obj.playlists[1].media[0].language + assert None is obj.playlists[1].media[0].language assert "Low" == obj.playlists[1].media[0].name assert "YES" == obj.playlists[1].media[0].default assert "YES" == obj.playlists[1].media[0].autoselect - assert None == obj.playlists[1].media[0].forced - assert None == obj.playlists[1].media[0].characteristics + assert None is obj.playlists[1].media[0].forced + assert None is obj.playlists[1].media[0].characteristics assert [] == obj.iframe_playlists @@ -546,8 +558,8 @@ def test_playlists_attribute_without_program_id(): assert "/url/1.m3u8" == obj.playlists[0].uri assert 320000 == obj.playlists[0].stream_info.bandwidth - assert None == obj.playlists[0].stream_info.codecs - assert None == obj.playlists[0].stream_info.program_id + assert None is obj.playlists[0].stream_info.codecs + assert None is obj.playlists[0].stream_info.program_id def test_playlists_attribute_with_resolution(): @@ -555,7 +567,7 @@ def test_playlists_attribute_with_resolution(): assert 2 == len(obj.playlists) assert (512, 288) == obj.playlists[0].stream_info.resolution - assert None == obj.playlists[1].stream_info.resolution + assert None is obj.playlists[1].stream_info.resolution def test_iframe_playlists_attribute(): @@ -588,9 +600,9 @@ def test_iframe_playlists_attribute(): assert "avc1.4d001f" == obj.iframe_playlists[0].iframe_stream_info.codecs assert "/url/2.m3u8" == obj.iframe_playlists[1].uri - assert None == obj.iframe_playlists[1].iframe_stream_info.program_id + assert None is obj.iframe_playlists[1].iframe_stream_info.program_id assert "120000" == obj.iframe_playlists[1].iframe_stream_info.bandwidth - assert None == obj.iframe_playlists[1].iframe_stream_info.resolution + assert None is obj.iframe_playlists[1].iframe_stream_info.resolution assert "avc1.4d400d" == obj.iframe_playlists[1].iframe_stream_info.codecs @@ -600,7 +612,7 @@ def test_version_attribute(): assert 2 == obj.version mock_parser_data(obj, {}) - assert None == obj.version + assert None is obj.version def test_version_settable_as_int(): @@ -623,7 +635,7 @@ def test_allow_cache_attribute(): assert "no" == obj.allow_cache mock_parser_data(obj, {}) - assert None == obj.allow_cache + assert None is obj.allow_cache def test_files_attribute_should_list_all_files_including_segments_and_key(): @@ -765,6 +777,13 @@ def test_dump_segment_honors_timespec(): assert "EXT-X-PROGRAM-DATE-TIME:2014-08-13T13:36:33.000000+00:00" in segment_text +def test_dump_honors_timespec(): + obj = m3u8.M3U8(playlists.SIMPLE_PLAYLIST_WITH_PROGRAM_DATE_TIME) + obj_text = obj.dumps(timespec="microseconds").strip() + + assert "EXT-X-PROGRAM-DATE-TIME:2014-08-13T13:36:33.000000+00:00" in obj_text + + def test_dump_should_not_ignore_zero_duration(): obj = m3u8.M3U8(playlists.SIMPLE_PLAYLIST_WITH_ZERO_DURATION) @@ -849,9 +868,7 @@ def test_should_dump_multiple_keys(): obj = m3u8.M3U8( playlists.PLAYLIST_WITH_ENCRYPTED_SEGMENTS_AND_IV_WITH_MULTIPLE_KEYS ) - expected = ( - playlists.PLAYLIST_WITH_ENCRYPTED_SEGMENTS_AND_IV_WITH_MULTIPLE_KEYS_SORTED.strip() - ) + expected = playlists.PLAYLIST_WITH_ENCRYPTED_SEGMENTS_AND_IV_WITH_MULTIPLE_KEYS_SORTED.strip() assert expected == obj.dumps().strip() @@ -880,9 +897,7 @@ def test_should_dump_complex_unencrypted_encrypted_keys_no_uri_attr(): obj = m3u8.M3U8( playlists.PLAYLIST_WITH_MULTIPLE_KEYS_UNENCRYPTED_AND_ENCRYPTED_NONE_AND_NO_URI_ATTR ) - expected = ( - playlists.PLAYLIST_WITH_MULTIPLE_KEYS_UNENCRYPTED_AND_ENCRYPTED_NONE_AND_NO_URI_ATTR.strip() - ) + expected = playlists.PLAYLIST_WITH_MULTIPLE_KEYS_UNENCRYPTED_AND_ENCRYPTED_NONE_AND_NO_URI_ATTR.strip() assert expected == obj.dumps().strip() @@ -1010,22 +1025,6 @@ def test_should_normalize_variant_streams_urls_if_base_path_passed_to_constructo assert obj.dumps().strip() == expected -def test_should_normalize_segments_and_key_urls_if_base_path_attribute_updated(): - base_path = "http://videoserver.com/hls/live" - - obj = m3u8.M3U8(playlists.PLAYLIST_WITH_ENCRYPTED_SEGMENTS_AND_IV) - obj.base_path = base_path # update later - - expected = ( - playlists.PLAYLIST_WITH_ENCRYPTED_SEGMENTS_AND_IV_SORTED.replace(", IV", ",IV") - .replace("../../../../hls", base_path) - .replace("/hls-key", base_path) - .strip() - ) - - assert obj.dumps() == expected - - def test_should_normalize_segments_and_key_urls_if_base_path_attribute_updated(): base_path = "http://videoserver.com/hls/live" @@ -1231,15 +1230,6 @@ def test_should_round_frame_rate(): assert expected == obj.dumps().strip() -@pytest.mark.skipif(sys.version_info >= (3,), reason="unicode not available in v3") -def test_m3u8_unicode_method(): - obj = m3u8.M3U8(playlists.SIMPLE_PLAYLIST) - - result = unicode(obj).strip() - expected = playlists.SIMPLE_PLAYLIST.strip() - assert result == expected - - def test_add_segment_to_playlist(): obj = m3u8.M3U8() @@ -1271,7 +1261,7 @@ def test_find_key_throws_when_no_match(): # deliberately empty ], ) - except KeyError as e: + except KeyError: threw = True finally: assert threw @@ -1580,12 +1570,6 @@ def test_dump_should_work_for_variant_playlists_with_image_playlists(): assert expected == obj.dumps().strip() -def test_dump_should_work_for_variant_playlists_with_image_playlists(): - obj = m3u8.M3U8(playlists.VARIANT_PLAYLIST_WITH_IMAGE_PLAYLISTS) - - expected = playlists.VARIANT_PLAYLIST_WITH_IMAGE_PLAYLISTS.strip() - - assert expected == obj.dumps().strip() def test_segment_media_sequence(): obj = m3u8.M3U8(playlists.SLIDING_WINDOW_PLAYLIST) diff --git a/tests/test_parser.py b/tests/test_parser.py index 7592b097..913e7e4e 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. @@ -163,8 +162,8 @@ def test_should_parse_variant_playlist(): data = m3u8.parse(playlists.VARIANT_PLAYLIST) playlists_list = list(data["playlists"]) - assert True == data["is_variant"] - assert None == data["media_sequence"] + assert True is data["is_variant"] + assert None is data["media_sequence"] assert 4 == len(playlists_list) assert "http://example.com/low.m3u8" == playlists_list[0]["uri"] @@ -181,8 +180,8 @@ def test_should_parse_variant_playlist_with_cc_subtitles_and_audio(): data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_CC_SUBS_AND_AUDIO) playlists_list = list(data["playlists"]) - assert True == data["is_variant"] - assert None == data["media_sequence"] + assert True is data["is_variant"] + assert None is data["media_sequence"] assert 2 == len(playlists_list) assert "http://example.com/with-cc-hi.m3u8" == playlists_list[0]["uri"] @@ -253,7 +252,7 @@ def test_should_parse_variant_playlist_with_iframe_playlists(): data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_IFRAME_PLAYLISTS) iframe_playlists = list(data["iframe_playlists"]) - assert True == data["is_variant"] + assert True is data["is_variant"] assert 4 == len(iframe_playlists) @@ -272,7 +271,7 @@ def test_should_parse_variant_playlist_with_alt_iframe_playlists_layout(): data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_ALT_IFRAME_PLAYLISTS_LAYOUT) iframe_playlists = list(data["iframe_playlists"]) - assert True == data["is_variant"] + assert True is data["is_variant"] assert 4 == len(iframe_playlists) @@ -290,7 +289,7 @@ def test_should_parse_variant_playlist_with_alt_iframe_playlists_layout(): def test_should_parse_iframe_playlist(): data = m3u8.parse(playlists.IFRAME_PLAYLIST) - assert True == data["is_i_frames_only"] + assert True is data["is_i_frames_only"] assert 4.12 == data["segments"][0]["duration"] assert "9400@376" == data["segments"][0]["byterange"] assert "segment1.ts" == data["segments"][0]["uri"] @@ -298,58 +297,62 @@ def test_should_parse_iframe_playlist(): def test_should_parse_variant_playlist_with_image_playlists(): data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_IMAGE_PLAYLISTS) - image_playlists = list(data['image_playlists']) + image_playlists = list(data["image_playlists"]) - assert True == data['is_variant'] + assert True is data["is_variant"] assert 2 == len(image_playlists) - assert '320x180' == image_playlists[0]['image_stream_info']['resolution'] - assert 'jpeg' == image_playlists[0]['image_stream_info']['codecs'] - assert '5x2_320x180/320x180-5x2.m3u8' == image_playlists[0]['uri'] - assert '640x360' == image_playlists[1]['image_stream_info']['resolution'] - assert 'jpeg' == image_playlists[1]['image_stream_info']['codecs'] - assert '5x2_640x360/640x360-5x2.m3u8' == image_playlists[1]['uri'] + assert "320x180" == image_playlists[0]["image_stream_info"]["resolution"] + assert "jpeg" == image_playlists[0]["image_stream_info"]["codecs"] + assert "5x2_320x180/320x180-5x2.m3u8" == image_playlists[0]["uri"] + assert "640x360" == image_playlists[1]["image_stream_info"]["resolution"] + assert "jpeg" == image_playlists[1]["image_stream_info"]["codecs"] + assert "5x2_640x360/640x360-5x2.m3u8" == image_playlists[1]["uri"] + def test_should_parse_vod_image_playlist(): data = m3u8.parse(playlists.VOD_IMAGE_PLAYLIST) - assert True == data['is_images_only'] - assert 8 == len(data['tiles']) - assert 'preroll-ad-1.jpg' == data['segments'][0]['uri'] - assert '640x360' == data['tiles'][0]['resolution'] - assert '5x2' == data['tiles'][0]['layout'] - assert 6.006 == data['tiles'][0]['duration'] - assert 'byterange' not in data['tiles'][0] + assert True is data["is_images_only"] + assert 8 == len(data["tiles"]) + assert "preroll-ad-1.jpg" == data["segments"][0]["uri"] + assert "640x360" == data["tiles"][0]["resolution"] + assert "5x2" == data["tiles"][0]["layout"] + assert 6.006 == data["tiles"][0]["duration"] + assert "byterange" not in data["tiles"][0] + def test_should_parse_vod_image_playlist2(): data = m3u8.parse(playlists.VOD_IMAGE_PLAYLIST2) - assert True == data['is_images_only'] - assert '640x360' == data['tiles'][0]['resolution'] - assert '4x3' == data['tiles'][0]['layout'] - assert 2.002 == data['tiles'][0]['duration'] - assert 6 == len(data['tiles']) - assert 'promo_1.jpg' == data['segments'][0]['uri'] + assert True is data["is_images_only"] + assert "640x360" == data["tiles"][0]["resolution"] + assert "4x3" == data["tiles"][0]["layout"] + assert 2.002 == data["tiles"][0]["duration"] + assert 6 == len(data["tiles"]) + assert "promo_1.jpg" == data["segments"][0]["uri"] + def test_should_parse_live_image_playlist(): data = m3u8.parse(playlists.LIVE_IMAGE_PLAYLIST) - assert True == data['is_images_only'] - assert 10 == len(data['segments']) - assert 'content-123.jpg' == data['segments'][0]['uri'] - assert 'content-124.jpg' == data['segments'][1]['uri'] - assert 'content-125.jpg' == data['segments'][2]['uri'] - assert 'missing-midroll.jpg' == data['segments'][3]['uri'] - assert 'missing-midroll.jpg' == data['segments'][4]['uri'] - assert 'missing-midroll.jpg' == data['segments'][5]['uri'] - assert 'content-128.jpg' == data['segments'][6]['uri'] - assert 'content-129.jpg' == data['segments'][7]['uri'] - assert 'content-130.jpg' == data['segments'][8]['uri'] - assert 'content-131.jpg' == data['segments'][9]['uri'] + assert True is data["is_images_only"] + assert 10 == len(data["segments"]) + assert "content-123.jpg" == data["segments"][0]["uri"] + assert "content-124.jpg" == data["segments"][1]["uri"] + assert "content-125.jpg" == data["segments"][2]["uri"] + assert "missing-midroll.jpg" == data["segments"][3]["uri"] + assert "missing-midroll.jpg" == data["segments"][4]["uri"] + assert "missing-midroll.jpg" == data["segments"][5]["uri"] + assert "content-128.jpg" == data["segments"][6]["uri"] + assert "content-129.jpg" == data["segments"][7]["uri"] + assert "content-130.jpg" == data["segments"][8]["uri"] + assert "content-131.jpg" == data["segments"][9]["uri"] + def test_should_parse_playlist_using_byteranges(): data = m3u8.parse(playlists.PLAYLIST_USING_BYTERANGES) - assert False == data["is_i_frames_only"] + assert False is data["is_i_frames_only"] assert 10 == data["segments"][0]["duration"] assert "76242@0" == data["segments"][0]["byterange"] assert "segment.ts" == data["segments"][0]["uri"] @@ -357,10 +360,10 @@ def test_should_parse_playlist_using_byteranges(): def test_should_parse_endlist_playlist(): data = m3u8.parse(playlists.SIMPLE_PLAYLIST) - assert True == data["is_endlist"] + assert True is data["is_endlist"] data = m3u8.parse(playlists.SLIDING_WINDOW_PLAYLIST) - assert False == data["is_endlist"] + assert False is data["is_endlist"] def test_should_parse_ALLOW_CACHE(): @@ -674,7 +677,7 @@ def parse_iptv_attributes(line, lineno, data, state): def test_tag_after_extinf(): parsed_playlist = m3u8.loads(playlists.IPTV_PLAYLIST_WITH_EARLY_EXTINF) actual = parsed_playlist.segments[0].uri - expected = 'http://str00.iptv.domain/7331/mpegts?token=longtokenhere' + expected = "http://str00.iptv.domain/7331/mpegts?token=longtokenhere" assert actual == expected @@ -770,8 +773,8 @@ def test_gap(): data = m3u8.parse(playlists.GAP_PLAYLIST) assert data["segments"][0]["gap_tag"] is None - assert data["segments"][1]["gap_tag"] == True - assert data["segments"][2]["gap_tag"] == True + assert data["segments"][1]["gap_tag"] is True + assert data["segments"][2]["gap_tag"] is True assert data["segments"][3]["gap_tag"] is None @@ -782,7 +785,7 @@ def test_gap_in_parts(): assert data["segments"][0]["parts"][0].get("gap", None) is None assert data["segments"][0]["parts"][1]["gap_tag"] is None assert data["segments"][0]["parts"][1]["gap"] == "YES" - assert data["segments"][0]["parts"][2]["gap_tag"] == True + assert data["segments"][0]["parts"][2]["gap_tag"] is True assert data["segments"][0]["parts"][2].get("gap", None) is None @@ -790,7 +793,7 @@ def test_should_parse_variant_playlist_with_iframe_with_average_bandwidth(): data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_IFRAME_AVERAGE_BANDWIDTH) iframe_playlists = list(data["iframe_playlists"]) - assert True == data["is_variant"] + assert True is data["is_variant"] assert 4 == len(iframe_playlists) @@ -813,7 +816,7 @@ def test_should_parse_variant_playlist_with_iframe_with_video_range(): data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_IFRAME_VIDEO_RANGE) iframe_playlists = list(data["iframe_playlists"]) - assert True == data["is_variant"] + assert True is data["is_variant"] assert 4 == len(iframe_playlists) @@ -831,7 +834,7 @@ def test_should_parse_variant_playlist_with_iframe_with_hdcp_level(): data = m3u8.parse(playlists.VARIANT_PLAYLIST_WITH_IFRAME_HDCP_LEVEL) iframe_playlists = list(data["iframe_playlists"]) - assert True == data["is_variant"] + assert True is data["is_variant"] assert 4 == len(iframe_playlists) @@ -869,13 +872,13 @@ def test_content_steering(): def test_cue_in_pops_scte35_data_and_duration(): data = m3u8.parse(playlists.CUE_OUT_ELEMENTAL_PLAYLIST) - assert data["segments"][9]["cue_in"] == True + assert data["segments"][9]["cue_in"] is True assert ( data["segments"][9]["scte35"] == "/DAlAAAAAAAAAP/wFAUAAAABf+//wpiQkv4ARKogAAEBAQAAQ6sodg==" ) assert data["segments"][9]["scte35_duration"] == "50" - assert data["segments"][10]["cue_in"] == False + assert data["segments"][10]["cue_in"] is False assert data["segments"][10]["scte35"] is None assert data["segments"][10]["scte35_duration"] is None diff --git a/tests/test_strict_validations.py b/tests/test_strict_validations.py index 73b40f7c..cf3bc09c 100644 --- a/tests/test_strict_validations.py +++ b/tests/test_strict_validations.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. diff --git a/tests/test_variant_m3u8.py b/tests/test_variant_m3u8.py index 5fcd6940..9e1001a0 100644 --- a/tests/test_variant_m3u8.py +++ b/tests/test_variant_m3u8.py @@ -1,4 +1,3 @@ -# coding: utf-8 # Copyright 2014 Globo.com Player authors. All rights reserved. # Use of this source code is governed by a MIT License # license that can be found in the LICENSE file. @@ -373,42 +372,59 @@ def test_create_a_variant_m3u8_with_iframe_with_hdcp_level_playlists(): def test_create_a_variant_m3u8_with_two_playlists_and_two_image_playlists(): variant_m3u8 = m3u8.M3U8() - subtitles = m3u8.Media('english_sub.m3u8', 'SUBTITLES', 'subs', 'en', - 'English', 'YES', 'YES', 'NO', None) + subtitles = m3u8.Media( + "english_sub.m3u8", + "SUBTITLES", + "subs", + "en", + "English", + "YES", + "YES", + "NO", + None, + ) variant_m3u8.add_media(subtitles) low_playlist = m3u8.Playlist( - uri='video-800k.m3u8', - stream_info={'bandwidth': 800000, - 'program_id': 1, - 'resolution': '624x352', - 'codecs': 'avc1.4d001f, mp4a.40.5', - 'subtitles': 'subs'}, + uri="video-800k.m3u8", + stream_info={ + "bandwidth": 800000, + "program_id": 1, + "resolution": "624x352", + "codecs": "avc1.4d001f, mp4a.40.5", + "subtitles": "subs", + }, media=[subtitles], - base_uri='http://example.com/' + base_uri="http://example.com/", ) high_playlist = m3u8.Playlist( - uri='video-1200k.m3u8', - stream_info={'bandwidth': 1200000, - 'program_id': 1, - 'codecs': 'avc1.4d001f, mp4a.40.5', - 'subtitles': 'subs'}, + uri="video-1200k.m3u8", + stream_info={ + "bandwidth": 1200000, + "program_id": 1, + "codecs": "avc1.4d001f, mp4a.40.5", + "subtitles": "subs", + }, media=[subtitles], - base_uri='http://example.com/' + base_uri="http://example.com/", ) low_image_playlist = m3u8.ImagePlaylist( - uri='thumbnails-sd.m3u8', - image_stream_info={'bandwidth': 151288, - 'resolution': '320x160', - 'codecs': 'jpeg'}, - base_uri='http://example.com/' + uri="thumbnails-sd.m3u8", + image_stream_info={ + "bandwidth": 151288, + "resolution": "320x160", + "codecs": "jpeg", + }, + base_uri="http://example.com/", ) high_image_playlist = m3u8.ImagePlaylist( - uri='thumbnails-hd.m3u8', - image_stream_info={'bandwidth': 193350, - 'resolution': '640x320', - 'codecs': 'jpeg'}, - base_uri='http://example.com/' + uri="thumbnails-hd.m3u8", + image_stream_info={ + "bandwidth": 193350, + "resolution": "640x320", + "codecs": "jpeg", + }, + base_uri="http://example.com/", ) variant_m3u8.add_playlist(low_playlist)