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") ]
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")]
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()
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")]
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")]
from bubblesub.cfg.menu import MenuCommand from .command import QualityCheckCommand COMMANDS = [QualityCheckCommand] MENU = [MenuCommand("&Quality check", "qc")]
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"), ], ) ]
) 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")]
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"), ], ) ]
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")]
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"), ]
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"), ], ) ]
@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")]
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")]
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")]