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,147 +0,0 @@
1
- import xml.etree.ElementTree as ET
2
-
3
- from auto_editor.timeline import Clip, v3
4
- from auto_editor.utils.func import aspect_ratio, to_timecode
5
-
6
- """
7
- Shotcut uses the MLT timeline format
8
-
9
- See docs here:
10
- https://mltframework.org/docs/mltxml/
11
-
12
- """
13
-
14
-
15
- def shotcut_write_mlt(output: str, tl: v3) -> None:
16
- mlt = ET.Element(
17
- "mlt",
18
- attrib={
19
- "LC_NUMERIC": "C",
20
- "version": "7.9.0",
21
- "title": "Shotcut version 22.09.23",
22
- "producer": "main_bin",
23
- },
24
- )
25
-
26
- width, height = tl.res
27
- num, den = aspect_ratio(width, height)
28
- tb = tl.tb
29
-
30
- ET.SubElement(
31
- mlt,
32
- "profile",
33
- attrib={
34
- "description": "automatic",
35
- "width": f"{width}",
36
- "height": f"{height}",
37
- "progressive": "1",
38
- "sample_aspect_num": "1",
39
- "sample_aspect_den": "1",
40
- "display_aspect_num": f"{num}",
41
- "display_aspect_den": f"{den}",
42
- "frame_rate_num": f"{tb.numerator}",
43
- "frame_rate_den": f"{tb.denominator}",
44
- "colorspace": "709",
45
- },
46
- )
47
-
48
- playlist_bin = ET.SubElement(mlt, "playlist", id="main_bin")
49
- ET.SubElement(playlist_bin, "property", name="xml_retain").text = "1"
50
-
51
- global_out = to_timecode(len(tl) / tb, "standard")
52
-
53
- producer = ET.SubElement(mlt, "producer", id="bg")
54
-
55
- ET.SubElement(producer, "property", name="length").text = global_out
56
- ET.SubElement(producer, "property", name="eof").text = "pause"
57
- ET.SubElement(producer, "property", name="resource").text = "#000" # background
58
- ET.SubElement(producer, "property", name="mlt_service").text = "color"
59
- ET.SubElement(producer, "property", name="mlt_image_format").text = "rgba"
60
- ET.SubElement(producer, "property", name="aspect_ratio").text = "1"
61
-
62
- playlist = ET.SubElement(mlt, "playlist", id="background")
63
- ET.SubElement(
64
- playlist,
65
- "entry",
66
- attrib={"producer": "bg", "in": "00:00:00.000", "out": global_out},
67
- ).text = "1"
68
-
69
- chains = 0
70
- producers = 0
71
-
72
- if tl.v:
73
- clips = [clip for clip in tl.v[0] if isinstance(clip, Clip)]
74
- elif tl.a:
75
- clips = tl.a[0]
76
- else:
77
- clips = []
78
-
79
- for clip in clips:
80
- src = clip.src
81
- length = to_timecode((clip.offset + clip.dur) / tb, "standard")
82
-
83
- if clip.speed == 1:
84
- resource = f"{src.path}"
85
- caption = f"{src.path.stem}"
86
- chain = ET.SubElement(
87
- mlt, "chain", attrib={"id": f"chain{chains}", "out": length}
88
- )
89
- else:
90
- chain = ET.SubElement(
91
- mlt, "producer", attrib={"id": f"producer{producers}", "out": length}
92
- )
93
- resource = f"{clip.speed}:{src.path}"
94
- caption = f"{src.path.stem} ({clip.speed}x)"
95
-
96
- producers += 1
97
-
98
- ET.SubElement(chain, "property", name="length").text = length
99
- ET.SubElement(chain, "property", name="resource").text = resource
100
-
101
- if clip.speed != 1:
102
- ET.SubElement(chain, "property", name="warp_speed").text = f"{clip.speed}"
103
- ET.SubElement(chain, "property", name="warp_pitch").text = "1"
104
- ET.SubElement(chain, "property", name="mlt_service").text = "timewarp"
105
-
106
- ET.SubElement(chain, "property", name="caption").text = caption
107
-
108
- chains += 1
109
-
110
- main_playlist = ET.SubElement(mlt, "playlist", id="playlist0")
111
- ET.SubElement(main_playlist, "property", name="shotcut:video").text = "1"
112
- ET.SubElement(main_playlist, "property", name="shotcut:name").text = "V1"
113
-
114
- producers = 0
115
- for i, clip in enumerate(clips):
116
- _in = to_timecode(clip.offset / tb, "standard")
117
- _out = to_timecode((clip.offset + clip.dur) / tb, "standard")
118
-
119
- tag_name = f"chain{i}"
120
- if clip.speed != 1:
121
- tag_name = f"producer{producers}"
122
- producers += 1
123
-
124
- ET.SubElement(
125
- main_playlist,
126
- "entry",
127
- attrib={"producer": tag_name, "in": _in, "out": _out},
128
- )
129
-
130
- tractor = ET.SubElement(
131
- mlt,
132
- "tractor",
133
- attrib={"id": "tractor0", "in": "00:00:00.000", "out": global_out},
134
- )
135
- ET.SubElement(tractor, "property", name="shotcut").text = "1"
136
- ET.SubElement(tractor, "property", name="shotcut:projectAudioChannels").text = "2"
137
- ET.SubElement(tractor, "track", producer="background")
138
- ET.SubElement(tractor, "track", producer="playlist0")
139
-
140
- tree = ET.ElementTree(mlt)
141
-
142
- ET.indent(tree, space="\t", level=0)
143
-
144
- if output == "-":
145
- print(ET.tostring(mlt, encoding="unicode"))
146
- else:
147
- tree.write(output, xml_declaration=True, encoding="utf-8")
auto_editor/ffwrapper.py DELETED
@@ -1,187 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from fractions import Fraction
5
- from pathlib import Path
6
-
7
- import bv
8
-
9
- from auto_editor.utils.log import Log
10
-
11
-
12
- def mux(input: Path, output: Path, stream: int) -> None:
13
- input_container = bv.open(input, "r")
14
- output_container = bv.open(output, "w")
15
-
16
- input_audio_stream = input_container.streams.audio[stream]
17
- output_audio_stream = output_container.add_stream("pcm_s16le")
18
-
19
- for frame in input_container.decode(input_audio_stream):
20
- output_container.mux(output_audio_stream.encode(frame))
21
-
22
- output_container.mux(output_audio_stream.encode(None))
23
-
24
- output_container.close()
25
- input_container.close()
26
-
27
-
28
- @dataclass(slots=True, frozen=True)
29
- class VideoStream:
30
- width: int
31
- height: int
32
- codec: str
33
- fps: Fraction
34
- duration: float
35
- sar: Fraction
36
- time_base: Fraction | None
37
- pix_fmt: str | None
38
- color_range: int
39
- color_space: int
40
- color_primaries: int
41
- color_transfer: int
42
- bitrate: int
43
- lang: str | None
44
-
45
-
46
- @dataclass(slots=True, frozen=True)
47
- class AudioStream:
48
- codec: str
49
- samplerate: int
50
- layout: str
51
- channels: int
52
- duration: float
53
- bitrate: int
54
- lang: str | None
55
-
56
-
57
- @dataclass(slots=True, frozen=True)
58
- class SubtitleStream:
59
- codec: str
60
- ext: str
61
- lang: str | None
62
-
63
-
64
- @dataclass(slots=True, frozen=True)
65
- class FileInfo:
66
- path: Path
67
- bitrate: int
68
- duration: float
69
- timecode: str # in SMPTE
70
- videos: tuple[VideoStream, ...]
71
- audios: tuple[AudioStream, ...]
72
- subtitles: tuple[SubtitleStream, ...]
73
-
74
- def get_res(self) -> tuple[int, int]:
75
- if self.videos:
76
- return self.videos[0].width, self.videos[0].height
77
- return 1920, 1080
78
-
79
- def get_fps(self) -> Fraction:
80
- if self.videos:
81
- return self.videos[0].fps
82
- return Fraction(30)
83
-
84
- def get_sr(self) -> int:
85
- if self.audios:
86
- return self.audios[0].samplerate
87
- return 48000
88
-
89
- @classmethod
90
- def init(self, path: str, log: Log) -> FileInfo:
91
- try:
92
- cont = bv.open(path, "r")
93
- except bv.error.FileNotFoundError:
94
- log.error(f"Input file doesn't exist: {path}")
95
- except bv.error.IsADirectoryError:
96
- log.error(f"Expected a media file, but got a directory: {path}")
97
- except bv.error.InvalidDataError:
98
- log.error(f"Invalid data when processing: {path}")
99
-
100
- videos: tuple[VideoStream, ...] = ()
101
- audios: tuple[AudioStream, ...] = ()
102
- subtitles: tuple[SubtitleStream, ...] = ()
103
-
104
- for v in cont.streams.video:
105
- if v.duration is not None and v.time_base is not None:
106
- vdur = float(v.duration * v.time_base)
107
- else:
108
- vdur = 0.0
109
-
110
- fps = v.average_rate
111
- if (fps is None or fps < 1) and v.name in {"png", "mjpeg", "webp"}:
112
- fps = Fraction(25)
113
- if fps is None or fps == 0:
114
- fps = Fraction(30)
115
-
116
- if v.sample_aspect_ratio is None:
117
- sar = Fraction(1)
118
- else:
119
- sar = v.sample_aspect_ratio
120
-
121
- cc = v.codec_context
122
-
123
- if v.name is None:
124
- log.error(f"Can't detect codec for video stream {v}")
125
-
126
- videos += (
127
- VideoStream(
128
- v.width,
129
- v.height,
130
- v.codec.canonical_name,
131
- fps,
132
- vdur,
133
- sar,
134
- v.time_base,
135
- getattr(v.format, "name", None),
136
- cc.color_range,
137
- cc.colorspace,
138
- cc.color_primaries,
139
- cc.color_trc,
140
- 0 if v.bit_rate is None else v.bit_rate,
141
- v.language,
142
- ),
143
- )
144
-
145
- for a in cont.streams.audio:
146
- adur = 0.0
147
- if a.duration is not None and a.time_base is not None:
148
- adur = float(a.duration * a.time_base)
149
-
150
- a_cc = a.codec_context
151
- audios += (
152
- AudioStream(
153
- a_cc.codec.canonical_name,
154
- 0 if a_cc.sample_rate is None else a_cc.sample_rate,
155
- a.layout.name,
156
- a_cc.channels,
157
- adur,
158
- 0 if a_cc.bit_rate is None else a_cc.bit_rate,
159
- a.language,
160
- ),
161
- )
162
-
163
- for s in cont.streams.subtitles:
164
- codec = s.codec_context.name
165
- sub_exts = {"mov_text": "srt", "ass": "ass", "webvtt": "vtt"}
166
- ext = sub_exts.get(codec, "vtt")
167
- subtitles += (SubtitleStream(codec, ext, s.language),)
168
-
169
- def get_timecode() -> str:
170
- for d in cont.streams.data:
171
- if (result := d.metadata.get("timecode")) is not None:
172
- return result
173
- for v in cont.streams.video:
174
- if (result := v.metadata.get("timecode")) is not None:
175
- return result
176
- return "00:00:00:00"
177
-
178
- timecode = get_timecode()
179
- bitrate = 0 if cont.bit_rate is None else cont.bit_rate
180
- dur = 0 if cont.duration is None else cont.duration / bv.time_base
181
-
182
- cont.close()
183
-
184
- return FileInfo(Path(path), bitrate, dur, timecode, videos, audios, subtitles)
185
-
186
- def __repr__(self) -> str:
187
- return f"@{self.path.name}"
auto_editor/help.py DELETED
@@ -1,223 +0,0 @@
1
- data = {
2
- "Auto-Editor": {
3
- "_": """
4
- Auto-Editor is an automatic video/audio creator and editor. By default, it will detect silence and create a new video with those sections cut out. By changing some of the options, you can export to a traditional editor like Premiere Pro and adjust the edits there, adjust the pacing of the cuts, and change the method of editing like using audio loudness and video motion to judge making cuts.
5
-
6
- Run:
7
- auto-editor --help
8
-
9
- To get the list of options.
10
- """.strip(),
11
- "--set-speed-for-range": """
12
- This option takes 3 arguments delimited with commas and they are as follows:
13
- - speed:
14
- - How fast to play the media (number)
15
- Start:
16
- - The time when speed first gets applied (time)
17
- End:
18
- - The time when speed stops being applied (time)
19
-
20
- example:
21
-
22
- --set-range-for-speed 2.5,400,800
23
-
24
- will set the speed from 400 ticks to 800 ticks to 2.5x
25
- If timebase is 30, 400 ticks to 800 means 13.33 to 26.66 seconds
26
- """.strip(),
27
- "--edit": """
28
- Evaluates a palet expression that returns a bool-array?. The array is then used for
29
- editing.
30
-
31
- Examples:
32
- --edit audio
33
- --edit audio:0.03 ; Change the threshold. Can be a value between 0-1.
34
- --edit audio:3% ; You can also use the `%` macro.
35
- --edit audio:0.03,stream=0 ; Only consider the first stream for editing.
36
- --edit audio:stream=1,threshold=0.05 ; Here's how you use keyword arguments.
37
- --edit (or audio:0.04,stream=0 audio:0.08,stream=1) ; Consider both streams for editing (merge with logical or), but with different thresholds.
38
- --edit motion
39
- --edit motion:0.02,blur=3
40
- --edit (or audio:0.04 motion:0.02,blur=3)
41
- --edit none
42
- --edit all/e
43
-
44
- Editing Methods:
45
- - audio ; Audio silence/loudness detection
46
- - threshold threshold? : 4%
47
- - stream (or/c nat? 'all) : 'all
48
- - mincut nat? : 6
49
- - minclip nat? : 3
50
-
51
- ; mincut is more significant, there it has a larger default value.
52
- ; minclip gets applied first, then mincut
53
-
54
- - motion ; Motion detection specialized for noisy real-life videos
55
- - threshold threshold? : 2%
56
- - stream nat? : 0
57
- - blur nat? : 9
58
- - width nat1? : 400
59
-
60
- - subtitle ; Detect when subtitle matches pattern as a RegEx string.
61
- - pattern string?
62
- - stream nat? : 0
63
- - ignore-case bool? : #f
64
- - max-count (or/c nat? void?) : (void)
65
-
66
- - none ; Do not modify the media in anyway; mark all sections as "loud" (1).
67
- - all/e ; Cut out everything out; mark all sections as "silent" (0).
68
- """.strip(),
69
- "--export": """
70
- This option controls how timelines are exported.
71
-
72
- Export Methods:
73
- - default ; Export as a regular media file
74
- - premiere ; Export as an XML timeline file for Adobe Premiere Pro
75
- - name : "Auto-Editor Media Group"
76
- - resolve ; Export as an XML timeline file for DaVinci Resolve
77
- - name : "Auto-Editor Media Group"
78
- - final-cut-pro ; Export as an XML timeline file for Final Cut Pro
79
- - name : "Auto-Editor Media Group"
80
- - shotcut ; Export as an XML timeline file for Shotcut
81
- - v3 ; Export as an auto-editor v3 timeline file
82
- - v1 ; Export as an auto-editor v1 timeline file
83
- - clip-sequence ; Export as multiple numbered media files
84
-
85
- """.strip(),
86
- "--player": """
87
- This option uses shell-like syntax to support using a specific player:
88
-
89
- auto-editor in.mp4 --player mpv
90
-
91
- Args for the player program can be added as well:
92
-
93
- auto-editor in.mp4 --player 'mpv --keep-open'
94
-
95
- Absolute or relative paths can also be used in the event the player's
96
- executable can not be resolved:
97
-
98
- auto-editor in.mp4 --player '/path/to/mpv'
99
- auto-editor in.mp4 --player './my-relative-path/mpv'
100
-
101
- If --player is not set, auto-editor will use the system default.
102
- If --no-open is used, --player will always be ignored.
103
-
104
- on MacOS, QuickTime can be used as the default player this way:
105
-
106
- auto-editor in.mp4 --player 'open -a "quicktime player"'
107
- """.strip(),
108
- "--resolution": """
109
-
110
- When working with media files, resolution will be based on the first input with a
111
- fallback value of 1920x1080
112
- """.strip(),
113
- "--frame-rate": """
114
- Set the timeline's timebase and the output media's frame rate.
115
-
116
- When working with media files, frame-rate will be the first input's frame rate
117
- with a fallback value of 30
118
-
119
- The format must be a string in the form:
120
- - frame_rate_num/frame_rate_den
121
- - an integer
122
- - an floating point number
123
- - a valid frame rate label
124
-
125
- The following labels are recognized:
126
- - ntsc -> 30000/1001
127
- - ntsc_film -> 24000/1001
128
- - pal -> 25
129
- - film -> 24
130
- """.strip(),
131
- "--temp-dir": """
132
- If not set, tempdir will be set with Python's tempfile module
133
- The directory doesn't have to exist beforehand, however, the root path must be valid.
134
- Beware that the temp directory can get quite big.
135
- """.strip(),
136
- "--audio-bitrate": """
137
- `--audio-bitrate` sets the target bitrate for the audio encoder.
138
- By default, the value is `auto` (let the encoder decide).
139
- It can be set to a natural number with units: ``, `k`, `K`, `M`, or `G`.
140
-
141
- """.strip(),
142
- "--video-bitrate": """
143
- `--video-bitrate` sets the target bitrate for the video encoder. `auto` is set as the default. It accepts the same format as `--audio-bitrate`
144
- """.strip(),
145
- "--margin": """
146
- Default value: 0.2s,0.2s
147
-
148
- `--margin` takes either one number of two numbers with a `,` in-between.
149
- The numbers may be written in the 'time' format. Here is a quick recap:
150
-
151
- frames / timebase : `` (no units)
152
- seconds : `s` `sec` `secs` `second` `seconds`
153
- minutes : `min` `mins` `minute` `minutes`
154
- hours : `hour`
155
-
156
- seconds, minutes : MM:SS.SS
157
- hours, mins, secs : HH:MM:SS.SS
158
-
159
-
160
- Setting margin examples:
161
- - `--margin 6`
162
- - `--margin 4,10`
163
- - `--margin 0.3s,0.5s`
164
- - `--margin 1:12.5` ; 1 minute, 12.5 seconds
165
-
166
- Behind the scenes, margin is a function that operates on boolean arrays
167
- (where 1 represents "loud" and 0 represents "silence")
168
-
169
- Here is a list of examples on how margin mutates boolean arrays
170
-
171
- (margin 0 0 (bool-array 0 0 0 1 0 0 0))
172
- > (array 'bool 0 0 0 1 0 0 0)
173
-
174
- (margin 1 0 (bool-array 0 0 0 1 0 0 0))
175
- > (array 'bool 0 0 1 1 0 0 0)
176
-
177
- (margin 1 1 (bool-array 0 0 0 1 0 0 0))
178
- > (array 'bool 0 0 1 1 1 0 0)
179
-
180
- (margin 1 2 (bool-array 0 0 1 1 0 0 0 0 1 0))
181
- > (array 'bool 0 1 1 1 1 1 0 1 1 1)
182
-
183
- (margin -2 2 (bool-array 0 0 1 1 0 0 0))
184
- > (array 'bool 0 0 0 0 1 1 0)
185
- """.strip(),
186
- "--audio-normalize": """
187
- Apply audio normalization after cutting.
188
-
189
- Normalization Methods:
190
- - ebu ; EBU R128 (double pass) loudness normalization
191
- ; Integrated loudness target
192
- - i (and/c (or/c int? float?) (between/c -70 -5)) : -24.0
193
- ; Loudness range target
194
- - lra (and/c (or/c int? float?) (between/c 1 50)) : 7.0
195
- ; Set maximum true peak
196
- - tp (and/c (or/c int? float?) (between/c -9 0)) : -2.0
197
- ; Set offset gain. Gain is applied before the true-peak limiter
198
- - gain (and/c (or/c int? float?) (between/c -99 99)) : 0.0
199
-
200
- - peak
201
- ; Loudness target
202
- - t (and/c (or/c int? float?) (between/c -99 0)) : -8.0
203
-
204
- If `#f` is chosen, no audio-normalization will be applied.
205
-
206
- Note that this option is a thin layer over the audio filter `loudnorm` for `ebu` and `astats`/`volume` for `peak` respectively.
207
- Check out its docs for more info: https://ffmpeg.org/ffmpeg-filters.html#loudnorm
208
-
209
- Examples:
210
- --audio-normalize #f
211
- --audio-normalize ebu:i=-5,lra=40,gain=5,tp=-1
212
- """.strip(),
213
- "--silent-speed": "99999 is the 'cut speed' and values over that or <=0 are considered 'cut speeds' as well",
214
- "--video-speed": "99999 is the 'cut speed' and values over that or <=0 are considered 'cut speeds' as well",
215
- },
216
- "info": {"_": "Retrieve information and properties about media files"},
217
- "levels": {"_": "Display loudness over time"},
218
- "subdump": {
219
- "_": "Dump text-based subtitles to stdout with formatting stripped out"
220
- },
221
- "desc": {"_": "Display a media's description metadata"},
222
- "test": {"_": "Self-Hosted Unit and End-to-End tests"},
223
- }
File without changes