Skip to content

Commit

Permalink
Drop Python 2 support; move to Python 3.7
Browse files Browse the repository at this point in the history
- added type hints
- added explicit keyword arguments to `SSAEvent`, `SSAStyle`
- bumped year in license statement
  • Loading branch information
tkarabela committed Oct 18, 2020
1 parent 29629fd commit 11d8264
Show file tree
Hide file tree
Showing 24 changed files with 230 additions and 221 deletions.
6 changes: 2 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8"
- "3.9"
install: pip install .
script: nosetests
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2014-2019 Tomas Karabela
Copyright (c) 2014-2020 Tomas Karabela

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 0 additions & 2 deletions docs/api-reference.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
API Reference
=============

.. note:: The documentation is written from Python 3 point of view; a "string" means Unicode string.

Supported input/output formats
------------------------------

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

# General information about the project.
project = u'pysubs2'
copyright = u'2014-2017, Tomas Karabela'
copyright = u'2014-2020, Tomas Karabela'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down
6 changes: 3 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ the native format of `Aegisub <http://www.aegisub.org/>`_; it also supports *Su
line.text = "{\\be1}" + line.text
subs.save("my_subtitles_edited.ass")

pysubs2 works with Python 2.7 and 3.4+. It’s available under the MIT license (see bottom of the page).
pysubs2 works with Python 3.7 or newer. It’s available under the MIT license (see bottom of the page).

To install pysubs2, just use `pip <https://pypi.python.org/pypi/pip>`_: ``pip install pysubs2``.
You can also clone `the GitHub repository <https://github.com/tkarabela/pysubs2/>`_ and install via ``python setup.py install``.
Expand All @@ -43,9 +43,9 @@ Documentation
License
-------

::
.. code-block:: text
Copyright (c) 2014-2019 Tomas Karabela
Copyright (c) 2014-2020 Tomas Karabela
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Now that we have a real file on the harddrive, let's import pysubs2 and load it.
>>> subs
<SSAFile with 2 events and 1 styles, last timestamp 0:02:00>

.. tip:: pysubs2 is written with Python 3 in mind, meaning that it speaks Unicode. By default, it uses UTF-8 when reading and writing files. Use the ``encoding`` keyword argument in case you need something else.
.. tip:: By default, pysubs2 uses UTF-8 encoding when reading and writing files. Use the ``encoding`` keyword argument in case you need something else.

Now we have a subtitle file, the :class:`pysubs2.SSAFile` object. It has two "events", ie. subtitles. You can treat ``subs`` as a list:

Expand Down
21 changes: 8 additions & 13 deletions pysubs2/cli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from __future__ import unicode_literals, print_function
import argparse
import codecs
import os
Expand All @@ -11,35 +10,35 @@
from .formats import get_file_extension, FORMAT_IDENTIFIERS
from .time import make_time
from .ssafile import SSAFile
from .common import PY3, VERSION
from .common import VERSION


def positive_float(s):
def positive_float(s: str) -> float:
x = float(s)
if not x > 0:
raise argparse.ArgumentTypeError("%r is not a positive number" % s)
return x

def character_encoding(s):
def character_encoding(s: str) -> str:
try:
codecs.lookup(s)
return s
except LookupError:
raise argparse.ArgumentError

def time(s):
def time(s: str):
d = {}
for v, k in re.findall(r"(\d*\.?\d*)(ms|m|s|h)", s):
d[k] = float(v)
return make_time(**d)


def change_ext(path, ext):
def change_ext(path: str, ext: str) -> str:
base, _ = op.splitext(path)
return base + ext


class Pysubs2CLI(object):
class Pysubs2CLI:
def __init__(self):
parser = self.parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
prog="pysubs2",
Expand Down Expand Up @@ -140,12 +139,8 @@ def main(self, argv):
with open(outpath, "w", encoding=args.output_enc) as outfile:
subs.to_file(outfile, output_format, args.fps)
else:
if PY3:
infile = io.TextIOWrapper(sys.stdin.buffer, args.input_enc)
outfile = io.TextIOWrapper(sys.stdout.buffer, args.output_enc)
else:
infile = io.TextIOWrapper(sys.stdin, args.input_enc)
outfile = io.TextIOWrapper(sys.stdout, args.output_enc)
infile = io.TextIOWrapper(sys.stdin.buffer, args.input_enc)
outfile = io.TextIOWrapper(sys.stdout.buffer, args.output_enc)

subs = SSAFile.from_file(infile, args.input_format, args.fps)
self.process(subs, args)
Expand Down
16 changes: 6 additions & 10 deletions pysubs2/common.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
from collections import namedtuple
import sys
from typing import Union


_Color = namedtuple("Color", "r g b a")


class Color(_Color):
"""
(r, g, b, a) namedtuple for 8-bit RGB color with alpha channel.
All values are ints from 0 to 255.
"""
def __new__(cls, r, g, b, a=0):
def __new__(cls, r: int, g: int, b: int, a: int=0):
for value in r, g, b, a:
if value not in range(256):
raise ValueError("Color channels must have values 0-255")

return _Color.__new__(cls, r, g, b, a)


#: Version of the pysubs2 library.
VERSION = "0.2.4"


PY3 = sys.version_info.major == 3

if PY3:
text_type = str
binary_string_type = bytes
else:
text_type = unicode
binary_string_type = str
IntOrFloat = Union[int, float]
5 changes: 5 additions & 0 deletions pysubs2/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
class Pysubs2Error(Exception):
"""Base class for pysubs2 exceptions."""


class UnknownFPSError(Pysubs2Error):
"""Framerate was not specified and couldn't be inferred otherwise."""


class UnknownFileExtensionError(Pysubs2Error):
"""File extension does not pertain to any known subtitle format."""


class UnknownFormatIdentifierError(Pysubs2Error):
"""Unknown subtitle format identifier (ie. string like ``"srt"``)."""


class FormatAutodetectionError(Pysubs2Error):
"""Subtitle format is ambiguous or unknown."""


class ContentNotUsable(Pysubs2Error):
"""Current content not usable for specified format"""
12 changes: 8 additions & 4 deletions pysubs2/formatbase.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
class FormatBase(object):
from typing import Optional
import io


class FormatBase:
"""
Base class for subtitle format implementations.
Expand All @@ -14,7 +18,7 @@ class FormatBase(object):
"""
@classmethod
def from_file(cls, subs, fp, format_, **kwargs):
def from_file(cls, subs, fp: io.TextIOBase, format_: str, **kwargs):
"""
Load subtitle file into an empty SSAFile.
Expand All @@ -37,7 +41,7 @@ def from_file(cls, subs, fp, format_, **kwargs):
raise NotImplementedError("Parsing is not supported for this format")

@classmethod
def to_file(cls, subs, fp, format_, **kwargs):
def to_file(cls, subs, fp: io.TextIOBase, format_: str, **kwargs):
"""
Write SSAFile into a file.
Expand All @@ -62,7 +66,7 @@ def to_file(cls, subs, fp, format_, **kwargs):
raise NotImplementedError("Writing is not supported for this format")

@classmethod
def guess_format(self, text):
def guess_format(self, text: str) -> Optional[str]:
"""
Return format identifier of recognized format, or None.
Expand Down
22 changes: 14 additions & 8 deletions pysubs2/formats.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from typing import Dict, Type

from .formatbase import FormatBase
from .microdvd import MicroDVDFormat
from .subrip import SubripFormat
from .jsonformat import JSONFormat
from .substation import SubstationFormat
from .mpl2 import MPL2Format
from .tmp import TmpFormat
from .webtt import WebTTFormat
from .webvtt import WebVTTFormat
from .exceptions import *

#: Dict mapping file extensions to format identifiers.
FILE_EXTENSION_TO_FORMAT_IDENTIFIER = {
FILE_EXTENSION_TO_FORMAT_IDENTIFIER: Dict[str, str] = {
".srt": "srt",
".ass": "ass",
".ssa": "ssa",
Expand All @@ -20,34 +22,37 @@
}

#: Dict mapping format identifiers to implementations (FormatBase subclasses).
FORMAT_IDENTIFIER_TO_FORMAT_CLASS = {
FORMAT_IDENTIFIER_TO_FORMAT_CLASS: Dict[str, Type[FormatBase]] = {
"srt": SubripFormat,
"ass": SubstationFormat,
"ssa": SubstationFormat,
"microdvd": MicroDVDFormat,
"json": JSONFormat,
"mpl2": MPL2Format,
"tmp": TmpFormat,
"vtt": WebTTFormat,
"vtt": WebVTTFormat,
}

FORMAT_IDENTIFIERS = list(FORMAT_IDENTIFIER_TO_FORMAT_CLASS.keys())

def get_format_class(format_):

def get_format_class(format_: str) -> Type[FormatBase]:
"""Format identifier -> format class (ie. subclass of FormatBase)"""
try:
return FORMAT_IDENTIFIER_TO_FORMAT_CLASS[format_]
except KeyError:
raise UnknownFormatIdentifierError(format_)

def get_format_identifier(ext):

def get_format_identifier(ext: str) -> str:
"""File extension -> format identifier"""
try:
return FILE_EXTENSION_TO_FORMAT_IDENTIFIER[ext]
except KeyError:
raise UnknownFileExtensionError(ext)

def get_file_extension(format_):

def get_file_extension(format_: str) -> str:
"""Format identifier -> file extension"""
if format_ not in FORMAT_IDENTIFIER_TO_FORMAT_CLASS:
raise UnknownFormatIdentifierError(format_)
Expand All @@ -58,7 +63,8 @@ def get_file_extension(format_):

raise RuntimeError("No file extension for format %r" % format_)

def autodetect_format(content):

def autodetect_format(content: str) -> str:
"""Return format identifier for given fragment or raise FormatAutodetectionError."""
formats = set()
for impl in FORMAT_IDENTIFIER_TO_FORMAT_CLASS.values():
Expand Down
10 changes: 2 additions & 8 deletions pysubs2/jsonformat.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from __future__ import unicode_literals, print_function

import json
from .common import Color, PY3
from .common import Color
from .ssaevent import SSAEvent
from .ssastyle import SSAStyle
from .formatbase import FormatBase
Expand Down Expand Up @@ -39,8 +37,4 @@ def to_file(cls, subs, fp, format_, **kwargs):
"events": [ev.as_dict() for ev in subs.events]
}

if PY3:
json.dump(data, fp)
else:
text = json.dumps(data, fp)
fp.write(unicode(text))
json.dump(data, fp)
5 changes: 1 addition & 4 deletions pysubs2/microdvd.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from __future__ import unicode_literals, print_function

from functools import partial
import re
from .common import text_type
from .exceptions import UnknownFPSError
from .ssaevent import SSAEvent
from .ssastyle import SSAStyle
Expand Down Expand Up @@ -86,7 +83,7 @@ def is_drawing(line):

# insert an artificial first line telling the framerate
if write_fps_declaration:
subs.insert(0, SSAEvent(start=0, end=0, text=text_type(fps)))
subs.insert(0, SSAEvent(start=0, end=0, text=str(fps)))

for line in subs:
if line.is_comment or is_drawing(line):
Expand Down
3 changes: 0 additions & 3 deletions pysubs2/mpl2.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# coding=utf-8

from __future__ import print_function, division, unicode_literals
import re

from .time import times_to_ms
Expand Down

0 comments on commit 11d8264

Please sign in to comment.