예제 #1
0
            func = self.api.video.current_stream.align_pts_to_next_frame
        else:
            assert False

        changed = 0
        unchanged = 0
        last_timecode = self.api.video.current_stream.timecodes[-1]

        with self.api.undo.capture():
            for sub in await self.args.target.get_subtitles():
                new_start = func(sub.start)
                new_end = func(sub.end)
                if new_end >= last_timecode:
                    new_end = last_timecode + 10

                if new_start != sub.start or new_end != sub.end:
                    sub.start = new_start
                    sub.end = new_end
                    changed += 1
                else:
                    unchanged += 1

        self.api.log.info(f"{changed} changed, {unchanged} unchanged")


COMMANDS = [AlignSubtitlesToVideoFramesCommand]
MENU = [
    MenuCommand("Align subtitles to &video frames",
                "align-subs-to-video-frames")
]
예제 #2
0
    async def run(self):
        changed = 0

        with self.api.undo.capture():
            subtitles = await self.args.target.get_subtitles()

            for sub in subtitles:
                style = self.api.subs.styles.get_by_name(sub.style)
                text = fix_text(sub.text, style)
                if text != sub.text:
                    sub.text = text
                    changed += 1

            self.api.log.info(f"fixed {changed} lines text")

            if self.args.smart_quotes:
                try:
                    changed = convert_to_smart_quotes(
                        subtitles,
                        self.args.opening_quotation_mark,
                        self.args.closing_quotation_mark,
                    )
                    self.api.log.info(f"fixed {changed} lines quotes")
                except ProcessingError as ex:
                    self.api.log.error(str(ex))


COMMANDS = [CleanCommand]
MENU = [MenuCommand("&Clean", "clean")]
예제 #3
0
        elif self.args.store_style:
            tagger.store_macro(MacroType.style, self.args.store_style)
        elif self.args.store_actor:
            tagger.store_macro(MacroType.actor, self.args.store_actor)
        elif self.args.store_text:
            tagger.store_macro(MacroType.text, self.args.store_text)
        elif self.args.apply:
            tagger.apply_macro(self.args.apply)


COMMANDS = [ActorsCommand]
MENU = [
    SubMenu(
        "Actors tagging",
        [
            MenuCommand("Enable", "actors on"),
            MenuCommand("Disable", "actors off"),
        ],
    )
]


def on_load(api: Api) -> None:
    global tagger
    if tagger is None:
        tagger = ActorsTagger(api)


def on_unload(_api: Api) -> None:
    global tagger
    tagger.disable()
예제 #4
0
END = "  {\\fnArial}\N{EIGHTH NOTE}{\\fn}"


class DecorateSongCommand(BaseCommand):
    names = ["decorate-song"]
    help_text = "Adds musical notes at the beginning and end of each line."

    @property
    def is_enabled(self):
        return self.args.target.makes_sense

    @staticmethod
    def decorate_parser(api: Api, parser: argparse.ArgumentParser) -> None:
        parser.add_argument(
            "-t",
            "--target",
            help="subtitles to process",
            type=lambda value: SubtitlesSelection(api, value),
            default="selected",
        )

    async def run(self):
        with self.api.undo.capture():
            for sub in await self.args.target.get_subtitles():
                sub.text = (START +
                            sub.text.replace(r"\N", fr"{END}\N{START}") + END)


COMMANDS = [DecorateSongCommand]
MENU = [MenuCommand("&Decorate song with notes", "decorate-song")]
예제 #5
0
    help_text = "Blurs selected subtitles."

    @property
    def is_enabled(self):
        return self.args.target.makes_sense

    @staticmethod
    def decorate_parser(api: Api, parser: argparse.ArgumentParser) -> None:
        parser.add_argument("-a",
                            "--amount",
                            help="amount to blur",
                            type=float,
                            default=0.75)
        parser.add_argument(
            "-t",
            "--target",
            help="subtitles to process",
            type=lambda value: SubtitlesSelection(api, value),
            default="selected",
        )

    async def run(self):
        with self.api.undo.capture():
            for sub in await self.args.target.get_subtitles():
                sub.text = (r"{\blur" + smart_float(self.args.amount) + "}" +
                            sub.text).replace("}{", "")


COMMANDS = [DecorateSongCommand]
MENU = [MenuCommand("&Blur selected subtitles", "blur")]
예제 #6
0
from bubblesub.cfg.menu import MenuCommand

from .command import QualityCheckCommand

COMMANDS = [QualityCheckCommand]
MENU = [MenuCommand("&Quality check", "qc")]
예제 #7
0
            type=int,
            default=3,
        )
        parser.add_argument(metavar="from",
                            dest="source_code",
                            help="source language code")
        parser.add_argument(
            metavar="to",
            dest="target_code",
            help="target language code",
            nargs="?",
            default="en",
        )


COMMANDS = [GoogleTranslateCommand]
MENU = [
    SubMenu(
        "&Translate",
        [
            MenuCommand("&Japanese", "tl ja"),
            MenuCommand("&German", "tl de"),
            MenuCommand("&French", "tl fr"),
            MenuCommand("&Italian", "tl it"),
            MenuCommand("&Polish", "tl pl"),
            MenuCommand("&Spanish", "tl es"),
            MenuCommand("&Auto", "tl auto"),
        ],
    )
]
예제 #8
0
        )
        parser.add_argument(
            "--end",
            help="where the sample should end",
            type=lambda value: Pts(api, value),
            default="a.e",
        )
        parser.add_argument(
            "-p",
            "--path",
            help="path to save the sample to",
            type=lambda value: FancyPath(api, value),
            default="",
        )
        parser.add_argument(
            "-i",
            "--include-subs",
            help='whether to "burn" subtitles into the video stream',
            action="store_true",
        )
        parser.add_argument(
            "--crf",
            help="constant rate factor parameter for ffmpeg (default: 20)",
            type=int,
            default=20,
        )


COMMANDS = [SaveVideoSampleCommand]
MENU = [MenuCommand("&Create video sample", "save-video-sample")]
예제 #9
0
            handle.seek(0, io.SEEK_SET)
            with sr.AudioFile(handle) as source:
                audio = recognizer.record(source)
            return recognizer.recognize_google(audio, language=self.args.code)

    @staticmethod
    def decorate_parser(api: Api, parser: argparse.ArgumentParser) -> None:
        parser.add_argument(
            "-t",
            "--target",
            help="subtitles to process",
            type=lambda value: SubtitlesSelection(api, value),
            default="selected",
        )
        parser.add_argument("code", help="language code")


COMMANDS = [SpeechRecognitionCommand]
MENU = [
    SubMenu(
        "&Speech recognition",
        [
            MenuCommand("&Japanese", "sr ja"),
            MenuCommand("&German", "sr de"),
            MenuCommand("&French", "sr fr"),
            MenuCommand("&Italian", "sr it"),
            MenuCommand("&Auto", "sr auto"),
        ],
    )
]
예제 #10
0
from pyqtcolordialog import QColorDialog

from bubblesub.api import Api
from bubblesub.api.cmd import BaseCommand
from bubblesub.cfg.menu import MenuCommand


class PickColorCommand(BaseCommand):
    names = ["pick-color"]
    help_text = "Runs a color dialog and prints ASS tags for it."

    async def run(self) -> None:
        await self.api.gui.exec(self._run_with_gui)

    async def _run_with_gui(self, main_window: QtWidgets.QMainWindow) -> None:
        color = QColorDialog.getColor(None, main_window)
        if color.isValid():
            self.api.log.info(f"RGB: #"
                              f"{color.red():02X}"
                              f"{color.green():02X}"
                              f"{color.blue():02X}"
                              f" ASS: \\c&H"
                              f"{color.blue():02X}"
                              f"{color.green():02X}"
                              f"{color.red():02X}"
                              f"&")


COMMANDS = [PickColorCommand]
MENU = [MenuCommand("&Pick color", "pick-color")]
예제 #11
0
class CleanClosedCaptionsCommand(BaseCommand):
    names = ["clean-cc"]
    help_text = (
        "Cleans common closed caption punctuation from the selected events."
    )

    async def run(self):
        with self.api.undo.capture():
            for line in self.api.subs.selected_events:
                note = line.note
                note = re.sub(r"\\N", "\n", note)
                note = re.sub(r"\(\(\)\)", "", note)  # retrospection
                note = re.sub(r"\([^\(\)]*\)", "", note)  # actors
                note = re.sub(r"\[[^\[\]]*\]", "", note)  # actors
                note = re.sub("[➡→]", "", note)  # line continuation
                note = re.sub("≪", "", note)  # distant dialogues
                note = re.sub("[<>《》]", "", note)
                note = re.sub("。", "。", note)  # half-width period
                note = re.sub("([…!?])。", r"\1", note)  # unneeded periods
                note = note.rstrip("・")
                note = re.sub(" ", "", note)  # Japanese doesn't need spaces
                note = note.strip()
                line.note = note


COMMANDS = [LoadClosedCaptionsCommand, CleanClosedCaptionsCommand]
MENU = [
    MenuCommand("&Load closed captions", "load-cc"),
    MenuCommand("&Clean closed captions", "clean-cc"),
]
예제 #12
0
            required=True,
        )
        parser.add_argument(
            "-f",
            "--from",
            dest="src",
            help="color to fade from",
            type=_parse_color,
        )
        parser.add_argument(
            "-t",
            "--to",
            dest="dst",
            help="color to fade to",
            type=_parse_color,
        )


COMMANDS = [FadeCommand]
MENU = [
    SubMenu(
        "&Fade from/to…",
        [
            MenuCommand("&Fade from black", "fade -d=2000 --from=101010"),
            MenuCommand("&Fade to black", "fade -d=2000 --to=101010"),
            MenuCommand("&Fade from white", "fade -d=2000 --from=FFFFFF"),
            MenuCommand("&Fade to white", "fade -d=2000 --to=FFFFFF"),
        ],
    )
]
예제 #13
0
    @staticmethod
    def decorate_parser(api: Api, parser: argparse.ArgumentParser) -> None:
        parser.add_argument(
            "-t",
            "--target",
            help="subtitles to process",
            type=lambda value: SubtitlesSelection(api, value),
            default="selected",
        )
        parser.add_argument(
            "-l",
            "--lang",
            "--language",
            help="language used for detection",
            default="jpn",
        )

    async def run(self):
        await self.api.gui.exec(self._run_with_gui)

    async def _run_with_gui(self, main_window: QtWidgets.QMainWindow) -> None:
        events = list(await self.args.target.get_subtitles())
        dialog = _Dialog(self.api, main_window, self.args.lang, events)
        with self.api.undo.capture():
            await async_dialog_exec(dialog)


COMMANDS = [OCRCommand]
MENU = [MenuCommand("&OCR", "ocr")]
예제 #14
0
        total_duration = 0
        words = []
        for event in self.api.subs.events:
            if event.actor.startswith("[") and event.actor.endswith("]"):
                continue
            total_duration += event.duration
            total_count += 1
            text = extract_text(event.text)
            if not text:
                empty_duration += event.duration
                empty_count += 1
            else:
                words += list(re.findall("[a-zA-Z]+", text))

        self.api.log.info(
            f"{empty_count} lines left ("
            f"{total_count - empty_count}/{max(1, total_count)}, "
            f"{(total_count - empty_count) / max(1, total_count):.01%})")
        self.api.log.info(
            f"{ms_to_str(empty_duration)} seconds left ("
            f"{ms_to_str(total_duration - empty_duration)}/"
            f"{ms_to_str(total_duration)}, "
            f"{(total_duration - empty_duration) / max(1, total_duration):.01%})"
        )
        self.api.log.info(f"{len(words)} words translated")
        self.api.log.info(f"{sum(map(len, words))} characters translated")


COMMANDS = [ProgressCommand]
MENU = [MenuCommand("Show translation &progress", "progress")]
예제 #15
0
            for event in self._events:
                event.text = f"{{\\an5\\pos({x},{y})}}" + event.text


class AlignKaraokeCommand(BaseCommand):
    names = ["align-karaoke"]
    help_text = (
        "Opens up a frame selection dialog and aligns karaoke line "
        "to the middle of the visual selection."
    )

    @property
    def is_enabled(self) -> bool:
        return (
            self.api.video.current_stream
            and self.api.video.current_stream.is_ready
            and self.api.playback.is_ready
            and self.api.subs.has_selection
        )

    async def run(self) -> None:
        await self.api.gui.exec(self._run_with_gui)

    async def _run_with_gui(self, main_window: QtWidgets.QMainWindow) -> None:
        dialog = _AlignKaraokeDialog(self.api, main_window)
        await async_dialog_exec(dialog)


COMMANDS = [AlignKaraokeCommand]
MENU = [MenuCommand("&Align karaoke", "align-karaoke")]