auto-editor 28.0.2__py3-none-any.whl → 29.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {auto_editor-28.0.2.dist-info → auto_editor-29.0.0.dist-info}/METADATA +5 -4
  2. auto_editor-29.0.0.dist-info/RECORD +5 -0
  3. auto_editor-29.0.0.dist-info/top_level.txt +1 -0
  4. auto_editor/__init__.py +0 -1
  5. auto_editor/__main__.py +0 -503
  6. auto_editor/analyze.py +0 -393
  7. auto_editor/cmds/__init__.py +0 -0
  8. auto_editor/cmds/cache.py +0 -69
  9. auto_editor/cmds/desc.py +0 -32
  10. auto_editor/cmds/info.py +0 -213
  11. auto_editor/cmds/levels.py +0 -199
  12. auto_editor/cmds/palet.py +0 -29
  13. auto_editor/cmds/repl.py +0 -113
  14. auto_editor/cmds/subdump.py +0 -72
  15. auto_editor/cmds/test.py +0 -812
  16. auto_editor/edit.py +0 -548
  17. auto_editor/exports/__init__.py +0 -0
  18. auto_editor/exports/fcp11.py +0 -195
  19. auto_editor/exports/fcp7.py +0 -313
  20. auto_editor/exports/json.py +0 -63
  21. auto_editor/exports/shotcut.py +0 -147
  22. auto_editor/ffwrapper.py +0 -187
  23. auto_editor/help.py +0 -223
  24. auto_editor/imports/__init__.py +0 -0
  25. auto_editor/imports/fcp7.py +0 -275
  26. auto_editor/imports/json.py +0 -234
  27. auto_editor/json.py +0 -297
  28. auto_editor/lang/__init__.py +0 -0
  29. auto_editor/lang/libintrospection.py +0 -10
  30. auto_editor/lang/libmath.py +0 -23
  31. auto_editor/lang/palet.py +0 -724
  32. auto_editor/lang/stdenv.py +0 -1184
  33. auto_editor/lib/__init__.py +0 -0
  34. auto_editor/lib/contracts.py +0 -235
  35. auto_editor/lib/data_structs.py +0 -278
  36. auto_editor/lib/err.py +0 -2
  37. auto_editor/make_layers.py +0 -315
  38. auto_editor/preview.py +0 -93
  39. auto_editor/render/__init__.py +0 -0
  40. auto_editor/render/audio.py +0 -517
  41. auto_editor/render/subtitle.py +0 -205
  42. auto_editor/render/video.py +0 -312
  43. auto_editor/timeline.py +0 -331
  44. auto_editor/utils/__init__.py +0 -0
  45. auto_editor/utils/bar.py +0 -142
  46. auto_editor/utils/chunks.py +0 -2
  47. auto_editor/utils/cmdkw.py +0 -206
  48. auto_editor/utils/container.py +0 -102
  49. auto_editor/utils/func.py +0 -128
  50. auto_editor/utils/log.py +0 -124
  51. auto_editor/utils/types.py +0 -277
  52. auto_editor/vanparse.py +0 -313
  53. auto_editor-28.0.2.dist-info/RECORD +0 -56
  54. auto_editor-28.0.2.dist-info/entry_points.txt +0 -6
  55. auto_editor-28.0.2.dist-info/top_level.txt +0 -2
  56. docs/build.py +0 -70
  57. {auto_editor-28.0.2.dist-info → auto_editor-29.0.0.dist-info}/WHEEL +0 -0
  58. {auto_editor-28.0.2.dist-info → auto_editor-29.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,102 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import TypedDict
5
-
6
- import bv
7
- from bv.codec import Codec
8
-
9
- from auto_editor.utils.log import Log
10
-
11
-
12
- class DictContainer(TypedDict, total=False):
13
- max_videos: int | None
14
- max_audios: int | None
15
- max_subtitles: int | None
16
- samplerate: list[int] | None
17
-
18
-
19
- @dataclass(slots=True)
20
- class Container:
21
- allow_image: bool
22
- vcodecs: set[str]
23
- acodecs: set[str]
24
- scodecs: set[str]
25
- default_vid: str
26
- default_aud: str
27
- default_sub: str
28
- max_videos: int | None = None
29
- max_audios: int | None = None
30
- max_subtitles: int | None = None
31
- samplerate: list[int] | None = None # Any samplerate is allowed
32
-
33
-
34
- containers: dict[str, DictContainer] = {
35
- "aac": {"max_audios": 1},
36
- "adts": {"max_audios": 1},
37
- "ass": {"max_subtitles": 1},
38
- "ssa": {"max_subtitles": 1},
39
- "apng": {"max_videos": 1},
40
- "gif": {"max_videos": 1},
41
- "wav": {"max_audios": 1},
42
- "ast": {"max_audios": 1},
43
- "mp3": {"max_audios": 1},
44
- "flac": {"max_audios": 1},
45
- "srt": {"max_subtitles": 1},
46
- "vtt": {"max_subtitles": 1},
47
- "swf": {"samplerate": [44100, 22050, 11025]},
48
- }
49
-
50
-
51
- def codec_type(x: str) -> str:
52
- if x in {"vp9", "vp8", "h264", "hevc", "av1", "gif", "apng"}:
53
- return "video"
54
- if x in {"aac", "flac", "mp3"}:
55
- return "audio"
56
- if x in {"ass", "ssa", "srt"}:
57
- return "subtitle"
58
-
59
- try:
60
- return Codec(x, "w").type
61
- except Exception:
62
- return ""
63
-
64
-
65
- def container_constructor(ext: str, log: Log) -> Container:
66
- try:
67
- container = bv.open(f".{ext}", "w")
68
- except ValueError:
69
- log.error(f"Could not find a suitable format for extension: {ext}")
70
-
71
- codecs = container.supported_codecs
72
- if ext == "webm":
73
- vdefault = "vp9"
74
- else:
75
- vdefault = container.default_video_codec
76
- adefault = container.default_audio_codec
77
- sdefault = container.default_subtitle_codec
78
- if sdefault == "none" and ext == "mp4":
79
- sdefault = "srt"
80
-
81
- container.close()
82
- vcodecs = set()
83
- acodecs = set()
84
- scodecs = set()
85
-
86
- for codec in codecs:
87
- if ext == "wav" and codec == "aac":
88
- continue
89
- kind = codec_type(codec)
90
- if kind == "video":
91
- vcodecs.add(codec)
92
- if kind == "audio":
93
- acodecs.add(codec)
94
- if kind == "subtitle":
95
- scodecs.add(codec)
96
-
97
- allow_image = ext in {"mp4", "mkv"}
98
- kwargs = containers[ext] if ext in containers else {}
99
-
100
- return Container(
101
- allow_image, vcodecs, acodecs, scodecs, vdefault, adefault, sdefault, **kwargs
102
- )
auto_editor/utils/func.py DELETED
@@ -1,128 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import TYPE_CHECKING
4
-
5
- import numpy as np
6
-
7
- from auto_editor.utils.log import Log
8
- from auto_editor.utils.types import split_num_str
9
-
10
- if TYPE_CHECKING:
11
- from collections.abc import Callable
12
- from fractions import Fraction
13
-
14
- from numpy.typing import NDArray
15
-
16
- BoolList = NDArray[np.bool_]
17
- BoolOperand = Callable[[BoolList, BoolList], BoolList]
18
-
19
-
20
- def boolop(a: BoolList, b: BoolList, call: BoolOperand) -> BoolList:
21
- if len(a) > len(b):
22
- k = np.copy(b)
23
- k.resize(len(a))
24
- b = k
25
- if len(b) > len(a):
26
- k = np.copy(a)
27
- k.resize(len(b))
28
- a = k
29
-
30
- return call(a, b)
31
-
32
-
33
- def to_timecode(secs: float | Fraction, fmt: str) -> str:
34
- sign = ""
35
- if secs < 0:
36
- sign = "-"
37
- secs = -secs
38
-
39
- _m, _s = divmod(secs, 60)
40
- _h, _m = divmod(_m, 60)
41
- s, m, h = float(_s), int(_m), int(_h)
42
-
43
- if fmt == "webvtt":
44
- if h == 0:
45
- return f"{sign}{m:02d}:{s:06.3f}"
46
- return f"{sign}{h:02d}:{m:02d}:{s:06.3f}"
47
- if fmt in {"srt", "mov_text"}:
48
- return f"{sign}{h:02d}:{m:02d}:" + f"{s:06.3f}".replace(".", ",", 1)
49
- if fmt == "standard":
50
- return f"{sign}{h:02d}:{m:02d}:{s:06.3f}"
51
- if fmt == "ass":
52
- return f"{sign}{h:d}:{m:02d}:{s:05.2f}"
53
- if fmt == "rass":
54
- return f"{sign}{h:d}:{m:02d}:{s:02.0f}"
55
-
56
- raise ValueError("to_timecode: Unreachable")
57
-
58
-
59
- def mut_margin(arr: BoolList, start_m: int, end_m: int) -> None:
60
- # Find start and end indexes
61
- start_index = []
62
- end_index = []
63
- arrlen = len(arr)
64
- for j in range(1, arrlen):
65
- if arr[j] != arr[j - 1]:
66
- if arr[j]:
67
- start_index.append(j)
68
- else:
69
- end_index.append(j)
70
-
71
- # Apply margin
72
- if start_m > 0:
73
- for i in start_index:
74
- arr[max(i - start_m, 0) : i] = True
75
- if start_m < 0:
76
- for i in start_index:
77
- arr[i : min(i - start_m, arrlen)] = False
78
-
79
- if end_m > 0:
80
- for i in end_index:
81
- arr[i : min(i + end_m, arrlen)] = True
82
- if end_m < 0:
83
- for i in end_index:
84
- arr[max(i + end_m, 0) : i] = False
85
-
86
-
87
- def get_stdout(cmd: list[str]) -> str:
88
- from subprocess import DEVNULL, PIPE, Popen
89
-
90
- stdout = Popen(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE).communicate()[0]
91
- return stdout.decode("utf-8", "replace")
92
-
93
-
94
- def get_stdout_bytes(cmd: list[str]) -> bytes:
95
- from subprocess import DEVNULL, PIPE, Popen
96
-
97
- return Popen(cmd, stdin=DEVNULL, stdout=PIPE, stderr=PIPE).communicate()[0]
98
-
99
-
100
- def aspect_ratio(width: int, height: int) -> tuple[int, int]:
101
- if height == 0:
102
- return (0, 0)
103
-
104
- def gcd(a: int, b: int) -> int:
105
- while b:
106
- a, b = b, a % b
107
- return a
108
-
109
- c = gcd(width, height)
110
- return width // c, height // c
111
-
112
-
113
- def parse_bitrate(input_: str, log: Log) -> int:
114
- try:
115
- val, unit = split_num_str(input_)
116
- except Exception as e:
117
- log.error(e)
118
-
119
- if unit.lower() == "k":
120
- return int(val * 1000)
121
- if unit == "M":
122
- return int(val * 1_000_000)
123
- if unit == "G":
124
- return int(val * 1_000_000_000)
125
- if unit == "":
126
- return int(val)
127
-
128
- log.error(f"Unknown bitrate: {input_}")
auto_editor/utils/log.py DELETED
@@ -1,124 +0,0 @@
1
- import sys
2
- from datetime import timedelta
3
- from shutil import get_terminal_size, rmtree
4
- from tempfile import mkdtemp
5
- from time import perf_counter, sleep
6
- from typing import NoReturn
7
-
8
-
9
- class Log:
10
- __slots__ = ("is_debug", "quiet", "machine", "no_color", "_temp", "_ut", "_s")
11
-
12
- def __init__(
13
- self,
14
- is_debug: bool = False,
15
- quiet: bool = False,
16
- temp_dir: str | None = None,
17
- machine: bool = False,
18
- no_color: bool = True,
19
- ):
20
- self.is_debug = is_debug
21
- self.quiet = quiet
22
- self.machine = machine
23
- self.no_color = no_color
24
- self._temp: str | None = None
25
- self._ut = temp_dir
26
- self._s = 0 if self.quiet or self.machine else perf_counter()
27
-
28
- def debug(self, message: object) -> None:
29
- if self.is_debug:
30
- self.conwrite("")
31
- sys.stderr.write(f"Debug: {message}\n")
32
-
33
- @property
34
- def temp(self) -> str:
35
- if self._temp is not None:
36
- return self._temp
37
-
38
- if self._ut is None:
39
- result = mkdtemp()
40
- else:
41
- import os.path
42
- from os import listdir, mkdir
43
-
44
- if os.path.isfile(self._ut):
45
- self.error("Temp directory cannot be an already existing file.")
46
-
47
- if os.path.isdir(self._ut):
48
- if len(listdir(self._ut)) != 0:
49
- self.error("Temp directory should be empty!")
50
- else:
51
- mkdir(self._ut)
52
- result = self._ut
53
-
54
- self.debug(f"Temp Directory: {result}")
55
- self._temp = result
56
- return result
57
-
58
- def cleanup(self) -> None:
59
- if self._temp is None:
60
- return
61
- try:
62
- rmtree(self._temp)
63
- self.debug("Removed Temp Directory.")
64
- except FileNotFoundError:
65
- pass
66
- except PermissionError:
67
- sleep(0.1)
68
- try:
69
- rmtree(self._temp)
70
- self.debug("Removed Temp Directory.")
71
- except Exception as e:
72
- self.debug(f"Failed to delete temp dir:\n{e}")
73
-
74
- def conwrite(self, message: str) -> None:
75
- if self.machine:
76
- print(message, flush=True)
77
- elif not self.quiet:
78
- buffer = " " * (get_terminal_size().columns - len(message) - 3)
79
- sys.stdout.write(f" {message}{buffer}\r")
80
-
81
- def print(self, message: str) -> None:
82
- if not self.quiet:
83
- self.conwrite("")
84
- sys.stdout.write(f"{message}\n")
85
-
86
- def warning(self, message: str) -> None:
87
- if not self.quiet:
88
- self.conwrite("")
89
- sys.stderr.write(f"Warning! {message}\n")
90
-
91
- def stop_timer(self) -> None:
92
- if not self.quiet and not self.machine:
93
- second_len = round(perf_counter() - self._s, 2)
94
- minute_len = timedelta(seconds=round(second_len))
95
-
96
- sys.stdout.write(f"Finished. took {second_len} seconds ({minute_len})\n")
97
-
98
- @staticmethod
99
- def deprecated(message: str) -> None:
100
- sys.stderr.write(f"\033[1m\033[33m{message}\033[0m\n")
101
-
102
- def error(self, message: str | Exception) -> NoReturn:
103
- if self.is_debug and isinstance(message, Exception):
104
- self.cleanup()
105
- raise message
106
-
107
- self.conwrite("")
108
- if self.no_color:
109
- sys.stderr.write(f"Error! {message}\n")
110
- else:
111
- sys.stderr.write(f"\033[31;40mError! {message}\033[0m\n")
112
-
113
- self.cleanup()
114
- from platform import system
115
-
116
- if system() == "Linux":
117
- sys.exit(1)
118
- else:
119
- try:
120
- sys.exit(1)
121
- except SystemExit:
122
- import os
123
-
124
- os._exit(1)
@@ -1,277 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
- from fractions import Fraction
5
-
6
-
7
- class CoerceError(Exception):
8
- pass
9
-
10
-
11
- def split_num_str(val: str | float) -> tuple[float, str]:
12
- if isinstance(val, float | int):
13
- return val, ""
14
-
15
- index = 0
16
- for char in val:
17
- if char not in "0123456789_ .-":
18
- break
19
- index += 1
20
- num, unit = val[:index], val[index:]
21
- try:
22
- float(num)
23
- except ValueError:
24
- raise CoerceError(f"Invalid number: '{val}'")
25
- return float(num), unit
26
-
27
-
28
- # Numbers: 0, 1, 2, 3, ...
29
- def natural(val: str | float) -> int:
30
- num, unit = split_num_str(val)
31
- if unit != "":
32
- raise CoerceError(f"'{val}': Natural does not allow units.")
33
- if not isinstance(num, int) and not num.is_integer():
34
- raise CoerceError(f"'{val}': Natural must be a valid integer.")
35
- if num < 0:
36
- raise CoerceError(f"'{val}': Natural cannot be negative.")
37
- return int(num)
38
-
39
-
40
- def number(val: str | float) -> float:
41
- if isinstance(val, str) and "/" in val:
42
- nd = val.split("/")
43
- if len(nd) != 2:
44
- raise CoerceError(f"'{val}': One divisor allowed.")
45
- vs = []
46
- for v in nd:
47
- try:
48
- vs.append(int(v))
49
- except ValueError:
50
- raise CoerceError(
51
- f"'{val}': Numerator and Denominator must be integers."
52
- )
53
- if vs[1] == 0:
54
- raise CoerceError(f"'{val}': Denominator must not be zero.")
55
- return vs[0] / vs[1]
56
-
57
- num, unit = split_num_str(val)
58
- if unit == "%":
59
- return num / 100
60
- if unit == "":
61
- return num
62
- raise CoerceError(f"Unknown unit: '{unit}'")
63
-
64
-
65
- def frame_rate(val: str) -> Fraction:
66
- if val == "ntsc":
67
- return Fraction(30000, 1001)
68
- if val == "ntsc_film":
69
- return Fraction(24000, 1001)
70
- if val == "pal":
71
- return Fraction(25)
72
- if val == "film":
73
- return Fraction(24)
74
- return Fraction(val)
75
-
76
-
77
- def time(val: str, tb: Fraction) -> int:
78
- if ":" in val:
79
- boxes = val.split(":")
80
- if len(boxes) == 2:
81
- return round((int(boxes[0]) * 60 + float(boxes[1])) * tb)
82
- if len(boxes) == 3:
83
- return round(
84
- (int(boxes[0]) * 3600 + int(boxes[1]) * 60 + float(boxes[2])) * tb
85
- )
86
- raise CoerceError(f"'{val}': Invalid time format")
87
-
88
- num, unit = split_num_str(val)
89
- if unit in {"s", "sec", "secs", "second", "seconds"}:
90
- return round(num * tb)
91
- if unit in {"min", "mins", "minute", "minutes"}:
92
- return round(num * tb * 60)
93
- if unit == "hour":
94
- return round(num * tb * 3600)
95
-
96
- if unit != "":
97
- raise CoerceError(f"'{val}': Time format got unknown unit: `{unit}`")
98
- if not num.is_integer():
99
- raise CoerceError(f"'{val}': Time format expects: int?")
100
- return int(num)
101
-
102
-
103
- def parse_color(val: str) -> str:
104
- """
105
- Convert a color str into an RGB tuple
106
-
107
- Accepts:
108
- - color names (black, red, blue)
109
- - 3 digit hex codes (#FFF, #3AE)
110
- - 6 digit hex codes (#3F0401, #005601)
111
- """
112
-
113
- color = val.lower()
114
-
115
- if color in colormap:
116
- color = colormap[color]
117
-
118
- if re.match("#[a-f0-9]{3}$", color):
119
- return "#" + "".join([x * 2 for x in color[1:]])
120
-
121
- if re.match("#[a-f0-9]{6}$", color):
122
- return color
123
-
124
- raise ValueError(f"Invalid Color: '{color}'")
125
-
126
-
127
- colormap = {
128
- # Taken from https://www.w3.org/TR/css-color-4/#named-color
129
- "aliceblue": "#f0f8ff",
130
- "antiquewhite": "#faebd7",
131
- "aqua": "#00ffff",
132
- "aquamarine": "#7fffd4",
133
- "azure": "#f0ffff",
134
- "beige": "#f5f5dc",
135
- "bisque": "#ffe4c4",
136
- "black": "#000000",
137
- "blanchedalmond": "#ffebcd",
138
- "blue": "#0000ff",
139
- "blueviolet": "#8a2be2",
140
- "brown": "#a52a2a",
141
- "burlywood": "#deb887",
142
- "cadetblue": "#5f9ea0",
143
- "chartreuse": "#7fff00",
144
- "chocolate": "#d2691e",
145
- "coral": "#ff7f50",
146
- "cornflowerblue": "#6495ed",
147
- "cornsilk": "#fff8dc",
148
- "crimson": "#dc143c",
149
- "cyan": "#00ffff",
150
- "darkblue": "#00008b",
151
- "darkcyan": "#008b8b",
152
- "darkgoldenrod": "#b8860b",
153
- "darkgray": "#a9a9a9",
154
- "darkgrey": "#a9a9a9",
155
- "darkgreen": "#006400",
156
- "darkkhaki": "#bdb76b",
157
- "darkmagenta": "#8b008b",
158
- "darkolivegreen": "#556b2f",
159
- "darkorange": "#ff8c00",
160
- "darkorchid": "#9932cc",
161
- "darkred": "#8b0000",
162
- "darksalmon": "#e9967a",
163
- "darkseagreen": "#8fbc8f",
164
- "darkslateblue": "#483d8b",
165
- "darkslategray": "#2f4f4f",
166
- "darkslategrey": "#2f4f4f",
167
- "darkturquoise": "#00ced1",
168
- "darkviolet": "#9400d3",
169
- "deeppink": "#ff1493",
170
- "deepskyblue": "#00bfff",
171
- "dimgray": "#696969",
172
- "dimgrey": "#696969",
173
- "dodgerblue": "#1e90ff",
174
- "firebrick": "#b22222",
175
- "floralwhite": "#fffaf0",
176
- "forestgreen": "#228b22",
177
- "fuchsia": "#ff00ff",
178
- "gainsboro": "#dcdcdc",
179
- "ghostwhite": "#f8f8ff",
180
- "gold": "#ffd700",
181
- "goldenrod": "#daa520",
182
- "gray": "#808080",
183
- "grey": "#808080",
184
- "green": "#008000",
185
- "greenyellow": "#adff2f",
186
- "honeydew": "#f0fff0",
187
- "hotpink": "#ff69b4",
188
- "indianred": "#cd5c5c",
189
- "indigo": "#4b0082",
190
- "ivory": "#fffff0",
191
- "khaki": "#f0e68c",
192
- "lavender": "#e6e6fa",
193
- "lavenderblush": "#fff0f5",
194
- "lawngreen": "#7cfc00",
195
- "lemonchiffon": "#fffacd",
196
- "lightblue": "#add8e6",
197
- "lightcoral": "#f08080",
198
- "lightcyan": "#e0ffff",
199
- "lightgoldenrodyellow": "#fafad2",
200
- "lightgreen": "#90ee90",
201
- "lightgray": "#d3d3d3",
202
- "lightgrey": "#d3d3d3",
203
- "lightpink": "#ffb6c1",
204
- "lightsalmon": "#ffa07a",
205
- "lightseagreen": "#20b2aa",
206
- "lightskyblue": "#87cefa",
207
- "lightslategray": "#778899",
208
- "lightslategrey": "#778899",
209
- "lightsteelblue": "#b0c4de",
210
- "lightyellow": "#ffffe0",
211
- "lime": "#00ff00",
212
- "limegreen": "#32cd32",
213
- "linen": "#faf0e6",
214
- "magenta": "#ff00ff",
215
- "maroon": "#800000",
216
- "mediumaquamarine": "#66cdaa",
217
- "mediumblue": "#0000cd",
218
- "mediumorchid": "#ba55d3",
219
- "mediumpurple": "#9370db",
220
- "mediumseagreen": "#3cb371",
221
- "mediumslateblue": "#7b68ee",
222
- "mediumspringgreen": "#00fa9a",
223
- "mediumturquoise": "#48d1cc",
224
- "mediumvioletred": "#c71585",
225
- "midnightblue": "#191970",
226
- "mintcream": "#f5fffa",
227
- "mistyrose": "#ffe4e1",
228
- "moccasin": "#ffe4b5",
229
- "navajowhite": "#ffdead",
230
- "navy": "#000080",
231
- "oldlace": "#fdf5e6",
232
- "olive": "#808000",
233
- "olivedrab": "#6b8e23",
234
- "orange": "#ffa500",
235
- "orangered": "#ff4500",
236
- "orchid": "#da70d6",
237
- "palegoldenrod": "#eee8aa",
238
- "palegreen": "#98fb98",
239
- "paleturquoise": "#afeeee",
240
- "palevioletred": "#db7093",
241
- "papayawhip": "#ffefd5",
242
- "peachpuff": "#ffdab9",
243
- "peru": "#cd853f",
244
- "pink": "#ffc0cb",
245
- "plum": "#dda0dd",
246
- "powderblue": "#b0e0e6",
247
- "purple": "#800080",
248
- "rebeccapurple": "#663399",
249
- "red": "#ff0000",
250
- "rosybrown": "#bc8f8f",
251
- "royalblue": "#4169e1",
252
- "saddlebrown": "#8b4513",
253
- "salmon": "#fa8072",
254
- "sandybrown": "#f4a460",
255
- "seagreen": "#2e8b57",
256
- "seashell": "#fff5ee",
257
- "sienna": "#a0522d",
258
- "silver": "#c0c0c0",
259
- "skyblue": "#87ceeb",
260
- "slateblue": "#6a5acd",
261
- "slategray": "#708090",
262
- "slategrey": "#708090",
263
- "snow": "#fffafa",
264
- "springgreen": "#00ff7f",
265
- "steelblue": "#4682b4",
266
- "tan": "#d2b48c",
267
- "teal": "#008080",
268
- "thistle": "#d8bfd8",
269
- "tomato": "#ff6347",
270
- "turquoise": "#40e0d0",
271
- "violet": "#ee82ee",
272
- "wheat": "#f5deb3",
273
- "white": "#ffffff",
274
- "whitesmoke": "#f5f5f5",
275
- "yellow": "#ffff00",
276
- "yellowgreen": "#9acd32",
277
- }