File: common.py

package info (click to toggle)
python-pysubs2 1.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,840 kB
  • sloc: python: 4,016; makefile: 163
file content (137 lines) | stat: -rw-r--r-- 3,587 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
from dataclasses import dataclass
from typing import Tuple, Union, Optional, Dict, Iterable, Iterator
from enum import IntEnum
import xml.etree.ElementTree as ET
from contextlib import contextmanager


@dataclass(init=False)
class Color:
    """
    8-bit RGB color with alpha channel.

    All values are ints from 0 to 255.
    """
    r: int
    g: int
    b: int
    a: int = 0

    def __init__(self, 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")

        self.r = r
        self.g = g
        self.b = b
        self.a = a


class Alignment(IntEnum):
    """
    An integer enum specifying text alignment

    The integer values correspond to Advanced SubStation Alpha definition (like on numpad).
    Note that the older SubStation Alpha (SSA) specification used different numbering schema.

    """
    BOTTOM_LEFT = 1
    BOTTOM_CENTER = 2
    BOTTOM_RIGHT = 3
    MIDDLE_LEFT = 4
    MIDDLE_CENTER = 5
    MIDDLE_RIGHT = 6
    TOP_LEFT = 7
    TOP_CENTER = 8
    TOP_RIGHT = 9

    @classmethod
    def from_ssa_alignment(cls, alignment: int) -> "Alignment":
        """Convert SSA alignment to ASS alignment"""
        return Alignment(SSA_ALIGNMENT.index(alignment) + 1)

    def to_ssa_alignment(self) -> int:
        """Convert ASS alignment to SSA alignment"""
        return SSA_ALIGNMENT[self.value - 1]


SSA_ALIGNMENT: Tuple[int, ...] = (1, 2, 3, 9, 10, 11, 5, 6, 7)


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


IntOrFloat = Union[int, float]


def etree_iter_child_nodes(elem: ET.Element) -> Iterator[Union[ET.Element, str]]:
    """
    Yield child text nodes (as str) and subelements for given XML element

    Workaround for awkward ``xml.etree.ElementTree`` API.

    See also:
        `etree_append_child_nodes()`

    """
    if elem.text:
        yield elem.text
    for child_elem in elem:
        yield child_elem
        if child_elem.tail:
            yield child_elem.tail


def etree_append_child_nodes(elem: ET.Element, nodes: Iterable[Union[ET.Element, str]]) -> None:
    """
    Add child text nodes and subelements to given XML element

    See also:
        `etree_iter_child_nodes()`

    """
    last_child = elem[-1] if len(elem) > 0 else None
    for node in nodes:
        if isinstance(node, str):
            if last_child is None:
                if elem.text is None:
                    elem.text = node
                else:
                    elem.text += node
            else:
                if last_child.tail is None:
                    last_child.tail = node
                else:
                    last_child.tail += node
        else:
            elem.append(node)
            last_child = node


@contextmanager
def etree_register_namespace_override() -> Iterator[None]:
    """
    Context manager that reverts global changes from ``xml.etree.ElementTree.register_namespace()``

    Workaround for poor namespace handling in ``xml.etree.ElementTree``.

    """
    namespace_map: Optional[Dict[str, str]] = None
    namespace_map_original_content = {}
    try:
        namespace_map = getattr(ET.register_namespace, "_namespace_map", None)
        if namespace_map is not None:
            namespace_map_original_content = namespace_map.copy()
    except Exception:
        pass

    yield

    try:
        if namespace_map is not None:
            namespace_map.clear()
            namespace_map.update(namespace_map_original_content)
    except Exception:
        pass