def _handle_indexed_bracket(self, bracket): split_range = bracket.content.split("-") if len(split_range) > 2: raise exceptions.InvalidRequest( f"Invalid start-end range: {self.content[0]}" ) start = int(split_range[0].strip()) end = start + 1 if len(split_range) > 1: end = int(split_range[1].strip()) + 1 # Human sintax lmao if start > end: raise exceptions.InvalidRequest(f"Negative index found: {split_range}") if (end - start) > 8: raise exceptions.InvalidRequest( f"Expected less than 9 items, found {end - start}" ) for index in range(start, end): logger.debug("Appending index: %d", index) # self.frames.append((self.subtitles[index - 1], 0)) if index > len(self.subtitles): raise exceptions.InvalidRequest(f"Index not found: {index}") self.frames.extend(bracket.process_subtitle(self.subtitles[index - 1])) self._unify_dialogue()
def _load_frames(self): if len(self.items) != 2: raise exceptions.InvalidRequest("Expected 2 items for swap") ids = [item.media.id for item in self.items] if ids[0] == ids[1]: raise exceptions.InvalidRequest("Can't swap the same movie") brackets = self._get_brackets() # Just left the last media item temp_item = self.items[-1] sliced = np.array_split(brackets, 2) source, dest = sliced for old, new in zip(source, dest): if not new.postproc.empty: new.update_from_swap(old) else: logger.debug("Ignoring swap for bracket: %s", new) frame_ = Frame(temp_item.media, new) frame_.load_frame() logger.debug("Appending frame: %s", frame_) self.frames.append(frame_) # For stories self._raw = self.frames[0].pil logger.debug("Loaded frames: %s", len(self.frames))
def _milli_cheks(self): try: self.milli -= self._args.get("minus", 0) self.milli += self._args.get("plus", 0) except TypeError: raise exceptions.InvalidRequest( f"Millisecond value is not an integer: {self._args}" ) from None if abs(self.milli) > 3000: raise exceptions.InvalidRequest("3000ms limit exceeded. Are you dumb?")
def _sanity_checks(self): if self.media.type == "song": raise exceptions.InvalidRequest( "Songs doesn't support GIF requests") if len(self.brackets) > 4: raise exceptions.InvalidRequest( f"Expected less than 5 quotes, found {len(self.brackets[0])}.") if len(self.brackets) > 1 and isinstance(self.brackets[0], tuple): raise exceptions.InvalidRequest( "No more than one range brackets are allowed")
def _check_image_size(cls, val): if val is None: return val try: value = float(val.strip()) except ValueError as error: raise exceptions.InvalidRequest(error) from None if value > 3: raise exceptions.InvalidRequest(f"Expected =<3, found {value}") return value
def _check_border(cls, val): if val is None: return None try: x_border, y_border = [int(item) for item in val.split(",")] except ValueError: raise exceptions.InvalidRequest(f"`{val}`") from None if any(item > 20 for item in (x_border, y_border)): raise exceptions.InvalidRequest("Expected `<20` value") return x_border, y_border
def _cv2_trim(self) -> bool: """ Remove black borders from a cv2 image array. This method is a f*****g waste of time as most sources are already properly cropped. We need to use it because of a few shitty WEB sources. F*****g unbelievable. :param cv2_image: cv2 image array """ logger.info("Trying to remove black borders with cv2") og_w, og_h = self._cv2.shape[1], self._cv2.shape[0] logger.debug("Original dimensions: %dx%d", og_w, og_h) og_quotient = og_w / og_h first_img = _remove_lateral_cv2(self._cv2) tmp_img = cv2.transpose(first_img) tmp_img = cv2.flip(tmp_img, flipCode=1) if tmp_img is None: raise exceptions.InvalidRequest("Possible all-black image found") final = _remove_lateral_cv2(tmp_img) out = cv2.transpose(final) final_img = cv2.flip(out, flipCode=0) if final_img is None: raise exceptions.InvalidRequest("Possible all-black image found") new_w, new_h = final_img.shape[1], final_img.shape[0] logger.debug("New dimensions: %dx%d", new_w, new_h) new_quotient = new_w / new_h if abs(new_quotient - og_quotient) > 0.9: logger.info("Possible bad quotient found: %s -> %s", og_quotient, new_quotient) return False width_percent = (100 / og_w) * new_w height_percent = (100 / og_h) * new_h if any(percent <= 65 for percent in (width_percent, height_percent)): logger.info("Possible bad trim found: %s -> %s", width_percent, height_percent) return False self._cv2 = final_img return True
def __init__( self, content, movie_list, episode_list, req_dictionary, multiple=False, ): search_func = search_episode if req_dictionary[ "is_episode"] else search_movie self.movie = search_func( episode_list if req_dictionary["is_episode"] else movie_list, req_dictionary["movie"], req_dictionary["parallel"] is None, ) self.discriminator, self.chain, self.quote = None, None, None self.pill = [] self.content = convert_request_content(content) self.req_dictionary = req_dictionary self.is_minute = self.content != content self.multiple = multiple self.dar = self.movie.get("dar") self.path = self.movie["path"] self.verified = req_dictionary["verified"] self.legacy_palette = "!palette" == self.req_dictionary["type"] if self.legacy_palette and len(req_dictionary["content"]) > 1: raise exceptions.InvalidRequest(req_dictionary["comment"])
def compute_brackets(self): "Find quotes, ranges, indexes, and timestamps." self._compute_brackets() if len(self.brackets) > 8: raise exceptions.InvalidRequest( f"Expected less than 8 frames, found {len(self.brackets)}")
def _get_box(val, limit=4) -> list: try: box = [int(item.strip()) for item in val.split(",")] except ValueError: raise exceptions.InvalidRequest( f"Non-int values found: {val}") from None if len(box) != limit: raise exceptions.InvalidRequest( f"Expected {limit} values, found {len(box)}") if any(0 < value > 100 for value in box): raise exceptions.InvalidRequest( f"Negative or greater than 100 value found: {box}") return box
def _get_frame_capture(self, timestamps: Tuple[int, int]): """ Get an image array based on seconds and milliseconds with cv2. """ # fixme path_ = (self.path or "").lower() if "hevc" in path_ or "265" in path_: raise exceptions.InvalidRequest( "This format of video is not available. Please wait for the upcoming Kinobot V3" ) if self.capture is None: self.load_capture_and_fps() seconds, milliseconds = timestamps extra_frames = int(self.fps * (milliseconds * 0.001)) frame_start = int(self.fps * seconds) + extra_frames logger.debug("Frame to extract: %s from %s", frame_start, self.path) self.capture.set(1, frame_start) frame = self.capture.read()[1] if frame is not None: if self._dar is None: self._dar = get_dar(self.path) return self._fix_dar(frame) raise exceptions.InexistentTimestamp(f"`{seconds}` not found in video")
def __init__( self, content, movie_list, episode_list, req_dictionary, multiple=False, ): self.on_demand = req_dictionary.get("on_demand", False) search_func = search_episode if req_dictionary[ "is_episode"] else search_movie raise_resting = ((req_dictionary["parallel"] is None) if not self.on_demand else not self.on_demand) self.movie = search_func( episode_list if req_dictionary["is_episode"] else movie_list, req_dictionary["movie"], raise_resting, ) self.discriminator, self.chain, self.quote = None, None, None self.pill = [] self.content = convert_request_content(content) self.req_dictionary = req_dictionary self.is_minute = self.content != content self.dar = self.movie.get("dar") self.path = self.movie["path"] self.verified = req_dictionary["verified"] self.legacy_palette = "!palette" == self.req_dictionary["type"] self.multiple = multiple or self.legacy_palette if self.legacy_palette and len(req_dictionary["content"]) > 1: raise exceptions.InvalidRequest( "Palette requests only support one bracket.")
def _check_ap(cls, val): if val is None: return None if 1 > val < 2.5: raise exceptions.InvalidRequest(f"Expected 1>|<2.5, found {val}") return val
def _check_dimensions(cls, val): if val is None: return None values = [number.strip() for number in val.split("x")] if len(values) != 2 or any(not val.isdigit() for val in values): raise exceptions.InvalidRequest(f"Invalid dimensions: {val}") values = int(values[0]), int(values[1]) if values not in _VALID_COLLAGES: raise exceptions.InvalidRequest( f"Invalid collage. Choose between: `{_VALID_COLLAGES}`") logger.debug("Found dimensions value: %s", values) return values
def subtitle(self) -> str: assert self.media.path is not None suffix = LANGUAGE_SUFFIXES.get(self._language) if suffix is None: raise exceptions.InvalidRequest( f"Language not found: {self._language}") return f"{os.path.splitext(self.media.path)[0]}.{suffix}.srt"
def from_request(cls, request): """Load an item from a Request class. :param request: :type request: Request """ assert request.type == "!gif" items = request.items if any(mreq.media.type == "song" for mreq in items): raise exceptions.InvalidRequest("Songs don't support GIF requests") if len(items) > 1: raise exceptions.InvalidRequest( "GIF requests don't support multiple items") item = items[0] item.compute_brackets() return cls(item.media, item.brackets, request.id)
def _get_gif_tuple(self): logger.debug("Loading GIF tuple: %s", self._content) tuple_ = [ _get_seconds(_sec.split(":")) for _sec in self._content.split("-") ] if len(tuple_) == 2: start, end = tuple_ if (end - start) > 7: raise exceptions.InvalidRequest( "Too long GIF request (expected less than 8 seconds)") if start > end: raise exceptions.InvalidRequest("Negative range found") self.content = tuple(tuple_) else: raise exceptions.InvalidRequest( f"Invalid GIF range request: {self._content}")
def _check_custom_crop(cls, val): if val is None: return val box = _get_box(val) if box[0] >= box[2] or box[1] >= box[3]: raise exceptions.InvalidRequest( "The next coordinate (e.g. left -> right) can't have an " f"equal or lesser value: {val}") return box
def __init__(self, query: str, filter_: str = "", limit: int = 10, lang="en"): if len(query.strip()) < 5: raise exceptions.InvalidRequest(f"Too short query (<5): {query}") self.query = query.strip() self.pattern = self.query self._glob_pattern = _glob_pattern_map[lang] logger.debug("Glob pattern: %s", self._glob_pattern) self.filter_ = filter_ self.limit = limit self.media_items: List[Union[Movie, Episode]] = [] self.items: List[dict] = []
def _check_image_rotate(cls, val): if val is None: return val try: value = float(val.strip()) except ValueError: return None if abs(value) > 360: raise exceptions.InvalidRequest(value) return value
def _check_glitch(cls, val): # --glitch glitch_amount=3,color_offset=True,scan_lines=True if val is None: return None glitch_dict = { "glitch_amount": 4, "color_offset": True, "scan_lines": True } fields = val.split(",") for field in fields: field_split = field.split("=") key = field_split[0] if key not in glitch_dict: continue if len(field_split) != 2: raise exceptions.InvalidRequest(f"`{field_split}`") if key == "glitch_amount": try: value = abs(int(field_split[-1])) if value > 10: raise exceptions.InvalidRequest( "Expected <10") from None except ValueError: raise exceptions.InvalidRequest( "Expected integer") from None glitch_dict["glitch_amount"] = value or 1 else: glitch_dict[key] = "true" in field_split[-1].lower() logger.debug("Updated glitch dict: %s", glitch_dict) return glitch_dict
def _extract_id_from_url(video_url: str) -> str: """ :param video_url: YouTube URL (classic or mobile) """ video_url = video_url.strip() parsed = parse.parse_qs(parse.urlparse(video_url).query).get("v") if parsed is not None: return parsed[0] # Mobile fallback if "youtu.be" in video_url: parsed = parse.urlsplit(video_url) return parsed.path.replace("/", "") raise exceptions.InvalidRequest(f"Invalid video URL: {video_url}")
def _get_seconds(split_timestamp: Sequence[str]) -> int: """ :param split_timestamp: :type split_timestamp: Sequence[str] :raises exceptions.InvalidRequest """ if len(split_timestamp) == 2: # mm:ss return int(split_timestamp[0]) * 60 + int(split_timestamp[1]) if len(split_timestamp) == 3: # hh:mm:ss return ((int(split_timestamp[0]) * 3600) + (int(split_timestamp[1]) * 60) + int(split_timestamp[2])) raise exceptions.InvalidRequest( f"Invalid format: {split_timestamp}. Use mm:ss or hh:mm:ss")
def _get_rg_pattern(self) -> str: """ Generate a punctuation-insensitive regex for ripgrep. """ if len(self.query) < 4: raise exceptions.InvalidRequest("Too short query (<4)") after_word = r"(\s|\W|$|(\W\s))" pattern = r"(^|\s|\W)" for word in self.query.split(): word = re.sub(r"\W", "", word) pattern = pattern + word + after_word logger.debug("Generated pattern: %s", pattern) return pattern
def __init__(self, items: Sequence[RequestItem], type_: str, id_: str, **kwargs): self.items = items self.id: str = id_ self.type: str = type_ self.frames: List[Frame] = [] self._paths = [] try: self.postproc = PostProc(**kwargs) except ValidationError as error: raise exceptions.InvalidRequest(error) from None self._raw: Optional[Image.Image] = None
def _check_apply_to(cls, val): if not val: # Falsy return None range_ = val.split("-") try: if len(range_) == 1: # --apply-to x num = int(range_[0].split(".")[0]) final = tuple(range(num - 1, num)) else: # --apply-to x-x final = tuple(range(int(range_[0]) - 1, int(range_[1]))) except ValueError: raise exceptions.InvalidRequest(f"`{range_}`") from None logger.debug("Parsed apply to: %s", final) return final
async def rate(self, ctx: commands.Context, *args): rating = args[-1].split("/")[0] try: rating = float(rating) except ValueError: raise exceptions.InvalidRequest( "Number not found: {rating}") from None logger.debug("Passed rating: %s", rating) movie = Movie.from_query(" ".join(args)) user = User.from_discord(ctx.author) user.rate_media(movie, rating) await ctx.send(f"You rating for `{movie.simple_title}`: **{rating}/5**" )
def _get_transparent_from_image_url(url: str) -> Image.Image: name = f"{uuid.uuid3(uuid.NAMESPACE_URL, url)}.png" path = os.path.join(CACHED_FRAMES_DIR, name) if not os.path.isfile(path): download_image(url, path) try: image = Image.open(path) _test_transparency_mask(image) except (ValueError, UnidentifiedImageError): raise exceptions.InvalidRequest( "Image has no transparent mask. If you can't find" " your desired image on Internet, upload your own to " "<https://imgur.com/> and use the generated URL.") from None image = image.crop(image.getbbox()) image.thumbnail((1280, 720)) return image
def __init__(self, media: hints, bracket: Bracket): self.media = media self.bracket = bracket self.message: Union[str, None] = None content = self.bracket.content if isinstance(content, Subtitle): self.seconds = content.start.seconds self.milliseconds = content.start.microseconds / 1000 self.message = content.content # Subtitle message elif isinstance(content, int): self.seconds = content self.milliseconds = bracket.milli else: raise exceptions.InvalidRequest( "Frames must contain quotes or timestamps") self._cv2: np.ndarray self.pil: Image.Image
def _image_list_check(self, frames): if (self.dimensions is not None and len(frames) != (self.dimensions[0] * self.dimensions[1]) # type: ignore and self.no_collage is False): raise exceptions.InvalidRequest( f"Kinobot returned {len(frames)} frames; such amount is compatible" f" with the requested collage dimensions: {self.dimensions}") logger.debug("Requested dimensions: %s", self.dimensions) if self.dimensions is None: self.dimensions = _POSSIBLES.get(len(frames)) # Still can be None if (self.dimensions is not None and self.dimensions in _LATERAL_COLLAGES and self.font_size == _DEFAULT_FONT_SIZE): self.font_size += 2 logger.debug("Found dimensions: %s", self.dimensions)