def test_check_durations_too_short_gap() -> None: event_list = AssEventList() event_list.append(AssEvent(start=0, end=500, text="test")) event_list.append(AssEvent(start=600, end=900, text="test")) results = list(check_durations(event_list[0])) assert len(results) == 1 assert results[0].text == "gap shorter than 250 ms (100 ms)"
def get_optimal_line_heights( api: Api, renderer: AssRenderer ) -> T.Dict[str, float]: TEST_LINE_COUNT = 20 VIDEO_RES_X = 100 VIDEO_RES_Y = TEST_LINE_COUNT * 300 fake_meta = AssMeta() fake_meta.set("WrapStyle", 2) renderer.set_source( style_list=api.subs.styles, event_list=api.subs.events, meta=fake_meta, video_resolution=(VIDEO_RES_X, VIDEO_RES_Y), ) ret = {} for style in api.subs.styles: event = AssEvent( start=0, end=1000, text="\\N".join(["gjMW"] * TEST_LINE_COUNT), style=style.name, ) _frame_width, frame_height = measure_frame_size(api, renderer, event) line_height = frame_height / TEST_LINE_COUNT ret[event.style] = line_height api.log.debug(f"average height for {event.style}: {line_height}") return ret
def update_preview(self) -> None: selected_style = self._selected_style if not selected_style: self._preview_box.clear() return resolution = (self._preview_box.width(), self._preview_box.height()) if resolution[0] <= 0 or resolution[1] <= 0: self._preview_box.clear() return fake_style = copy(selected_style) fake_style.name = "Default" if ( self._api.video.current_stream and self._api.video.current_stream.is_ready ): fake_style.scale( resolution[1] / self._api.video.current_stream.height ) fake_style_list = AssStyleList() fake_style_list.append(fake_style) fake_event = AssEvent( start=0, end=1000, text=self.preview_text.replace("\n", "\\N"), style=fake_style.name, ) fake_event_list = AssEventList() fake_event_list.append(fake_event) fake_meta = AssMeta() image = PIL.Image.new(mode="RGBA", size=resolution) background_path = self._background_combobox.currentData() if background_path and background_path.exists(): background = PIL.Image.open(background_path) for y in range(0, resolution[1], background.height): for x in range(0, resolution[0], background.width): image.paste(background, (x, y)) self._renderer.set_source( fake_style_list, fake_event_list, fake_meta, resolution ) subs_image = self._renderer.render( time=0, aspect_ratio=( self._api.video.current_stream.aspect_ratio if self._api.video.current_stream else 1 ), ) image = PIL.Image.composite(subs_image, image, subs_image) image = PIL.ImageQt.ImageQt(image) image = QtGui.QImage(image) self._preview_box.setPixmap(QtGui.QPixmap.fromImage(image))
def test_convert_to_smart_quotes() -> None: events = [ AssEvent(text='"Infix". "Prefix…'), AssEvent(text="ignore"), AssEvent(text='…suffix"'), ] convert_to_smart_quotes(events, "„", "”") assert events[0].text == "„Infix”. „Prefix…" assert events[1].text == "ignore" assert events[2].text == "…suffix”" with pytest.raises(ProcessingError, match="uneven double quotation mark count"): convert_to_smart_quotes( [AssEvent(text='"uneven quotation mark count')], "„", "”")
def test_check_punctuation(text: str, violation_text: T.Optional[str]) -> None: event = AssEvent(text=text) results = list(check_punctuation("en_US", event)) if violation_text is None: assert len(results) == 0 else: assert len(results) == 1 assert results[0].text == violation_text
def test_check_double_words(text, violation_text): event_list = AssEventList() event_list.append(AssEvent(text=text)) results = list(check_double_words(event_list[0])) if violation_text is None: assert len(results) == 0 else: assert len(results) == 1 assert results[0].text == violation_text
def test_check_ass_tags(text, violation_text_re): event_list = AssEventList() event_list.append(AssEvent(text=text)) results = list(check_ass_tags(event_list[0])) if violation_text_re is None: assert len(results) == 0 else: assert len(results) == 1 assert re.match(violation_text_re, results[0].text)
def _events_section_handler( line: str, ass_file: AssFile, ctx: _ReadContext ) -> None: if line.startswith("Format:"): _, rest = line.split(": ", 1) ctx.field_names = [p.strip() for p in rest.split(",")] return event_type, rest = line.split(": ", 1) field_values = rest.strip().split(",", len(ctx.field_names) - 1) field_dict = dict(zip(ctx.field_names, field_values)) if event_type not in {"Comment", "Dialogue"}: raise ValueError(f'unknown event type: "{event_type}"') text = field_dict["Text"] note = "" match = re.search(r"{NOTE:(?P<note>[^}]*)}", text) if match: text = text[: match.start()] + text[match.end() :] note = unescape_ass_tag(match.group("note")) # ASS tags have centisecond precision start = _timestamp_to_ms(field_dict["Start"]) end = _timestamp_to_ms(field_dict["End"]) # refine times down to millisecond precision using novelty {TIME:…} tag, # but only if the times match the regular ASS times. This is so that # subtitle times modified outside of bubblesub with editors that do not # write the novelty {TIME:…} tag are not overwritten. match = re.search(r"{TIME:(?P<start>-?\d+),(?P<end>-?\d+)}", text) if match: text = text[: match.start()] + text[match.end() :] start_ms = int(match.group("start")) end_ms = int(match.group("end")) if 0 <= start_ms - start < 10: start = start_ms if 0 <= end_ms - end < 10: end = end_ms ass_file.events.append( AssEvent( layer=int(field_dict["Layer"]), start=start, end=end, style=field_dict["Style"], actor=field_dict["Name"], margin_left=int(field_dict["MarginL"]), margin_right=int(field_dict["MarginR"]), margin_vertical=int(field_dict["MarginV"]), effect=field_dict["Effect"], text=text, note=note, is_comment=event_type == "Comment", ) )
def test_check_quotes( text: str, expected_violations: T.List[T.Tuple[str, LogLevel]]) -> None: event_list = AssEventList() event_list.append(AssEvent(text=text)) results = list(check_quotes(event_list[0])) assert len(results) == len(expected_violations) for expected_violation, result in zip(expected_violations, results): violation_text_re, log_level = expected_violation assert re.match(violation_text_re, result.text) assert result.log_level == log_level
def test_check_line_continuation(texts: T.List[str], violation_text: T.Optional[str]) -> None: event_list = AssEventList() for text in texts: event_list.append(AssEvent(text=text)) results = [] for event in event_list: results += list(check_line_continuation(event)) if violation_text is None: assert len(results) == 0 else: assert len(results) == 1 assert results[0].text == violation_text
def test_check_unnecessary_breaks(text, violation_text): api = MagicMock() api.video.current_stream.aspect_ratio = 1 api.subs.meta = {"PlayResX": 1280} with patch( "quality_check.check_unnecessary_breaks.measure_frame_size", return_value=(100, 0), ): event_list = AssEventList() event_list.append(AssEvent(text=text)) renderer = MagicMock() results = list(check_unnecessary_breaks(event_list[0], api, renderer)) if violation_text is None: assert len(results) == 0 else: assert len(results) == 1 assert results[0].text == violation_text
async def _run(self, main_window: QtWidgets.QMainWindow) -> None: path = load_dialog( main_window, "Subtitles (*.ass *.srt);;All files (*.*)" ) if not path: return source = pysubs2.load(str(path)) with self.api.undo.capture(): for line in source: self.api.subs.events.append( AssEvent( start=line.start, end=line.end, note=line.text, style=self.api.subs.default_style_name, ) )
def create_new_subtitle(api: Api, pts: int, by_end: bool) -> None: current_video_stream = api.video.current_stream if current_video_stream: pts = current_video_stream.align_pts_to_near_frame(pts) insertion_point = get_subtitle_insertion_point(api, pts, by_end) if current_video_stream: insertion_point.start = current_video_stream.align_pts_to_near_frame( insertion_point.start) insertion_point.end = current_video_stream.align_pts_to_near_frame( insertion_point.end) api.subs.events.insert( insertion_point.idx, AssEvent( start=insertion_point.start, end=insertion_point.end, style=api.subs.default_style_name, ), ) api.subs.selected_indexes = [insertion_point.idx]
async def run(self) -> None: indexes = await self.args.origin.get_indexes() if self.args.dir == "before": idx, start, end = self._insert_before(indexes) elif self.args.dir == "after": idx, start, end = self._insert_after(indexes) else: raise AssertionError if not self.args.no_align and self.api.video.current_stream: start = self.api.video.current_stream.align_pts_to_near_frame( start) end = self.api.video.current_stream.align_pts_to_near_frame(end) with self.api.undo.capture(): self.api.subs.events.insert( idx, AssEvent( start=start, end=end, style=self.api.subs.default_style_name, ), ) self.api.subs.selected_indexes = [idx]
def test_check_durations_comment() -> None: event = AssEvent(start=0, end=100, text="test", is_comment=True) assert len(list(check_durations(event))) == 0
def test_check_durations_empty_text() -> None: event = AssEvent(start=0, end=100) assert len(list(check_durations(event))) == 0
def set_subject_text(self, sub: AssEvent, value: str) -> None: sub.actor = value
def test_violation_single_event() -> None: event_list = AssEventList() event_list.append(AssEvent(start=0, end=0)) violation = Violation(event_list[0], "test") assert repr(violation) == "#1: test"
def set_subject_text(self, sub: AssEvent, value: str) -> None: sub.style = value
def test_check_durations_too_short_long_text() -> None: event = AssEvent(start=0, end=100, text="test test test test test") results = list(check_durations(event)) assert len(results) == 1 assert results[0].text == "duration shorter than 500 ms"
def test_check_durations_good_duration() -> None: event = AssEvent(start=0, end=501, text="test test test test test") assert len(list(check_durations(event))) == 0
def set_subject_text(self, sub: AssEvent, value: str) -> None: sub.note = value.replace("\n", "\\N")
def test_check_durations_good_gap() -> None: event_list = AssEventList() event_list.append(AssEvent(start=0, end=500, text="test")) event_list.append(AssEvent(start=750, end=900, text="test")) assert len(list(check_durations(event_list[0]))) == 0
def test_violation_multiple_events() -> None: event_list = AssEventList() event_list.append(AssEvent(start=0, end=0)) event_list.append(AssEvent(start=0, end=0)) violation = Violation([event_list[0], event_list[1]], "test") assert repr(violation) == "#1+#2: test"