Exemplo n.º 1
0
def save_channel_branding(channels_dir, channel_id, save_banner=False):
    """ download, save and resize profile [and banner] of a channel """
    channel_json = get_channel_json(channel_id)

    thumbnails = channel_json["snippet"]["thumbnails"]
    for quality in ("medium",
                    "default"):  # high:800px, medium:240px, default:88px
        if quality in thumbnails.keys():
            thumnbail = thumbnails[quality]["url"]
            break

    channel_dir = channels_dir.joinpath(channel_id)
    channel_dir.mkdir(exist_ok=True)

    profile_path = channel_dir.joinpath("profile.jpg")
    if not profile_path.exists():
        stream_file(thumnbail, profile_path)
        # resize profile as we only use up 100px/80 sq
        resize_image(profile_path, width=100, height=100)

    # currently disabled as per deprecation of the following property
    # without an alternative way to retrieve it (using the API)
    # See: https://developers.google.com/youtube/v3/revision_history#september-9,-2020
    if save_banner and False:
        banner = channel_json["brandingSettings"]["image"]["bannerImageUrl"]
        banner_path = channel_dir.joinpath("banner.jpg")
        if not banner_path.exists():
            stream_file(banner, banner_path)
Exemplo n.º 2
0
    def add_illustrations(self):
        src_illus_fpath = self.build_dir / "illustration"

        # if user provided a custom favicon, retrieve that
        if not self.conf.favicon:
            self.conf.favicon = Global.site["BadgeIconUrl"]
        handle_user_provided_file(source=self.conf.favicon,
                                  dest=src_illus_fpath)

        # convert to PNG (might already be PNG but it's OK)
        illus_fpath = src_illus_fpath.with_suffix(".png")
        convert_image(src_illus_fpath, illus_fpath)

        # resize to appropriate size (ZIM uses 48x48 so we double for retina)
        for size in (96, 48):
            resize_image(illus_fpath,
                         width=size,
                         height=size,
                         method="thumbnail")
            with open(illus_fpath, "rb") as fh:
                Global.creator.add_illustration(size, fh.read())

        # download and add actual favicon (ICO file)
        favicon_fpath = self.build_dir / "favicon.ico"
        handle_user_provided_file(source=Global.site["IconUrl"],
                                  dest=favicon_fpath)
        Global.creator.add_item_for("favicon.ico",
                                    fpath=favicon_fpath,
                                    is_front=False)

        # download apple-touch-icon
        Global.creator.add_item(
            URLItem(url=Global.site["BadgeIconUrl"],
                    path="apple-touch-icon.png"))
Exemplo n.º 3
0
    def update_metadata(self):
        # we use title, description, profile and banner of channel/user
        # or channel of first playlist
        main_channel_json = get_channel_json(self.main_channel_id)
        save_channel_branding(self.channels_dir,
                              self.main_channel_id,
                              save_banner=True)
        self.copy_default_banner(self.main_channel_id)

        # if a single playlist was requested, use if for names;
        # otherwise, use main_channel's details.
        auto_title = (self.playlists[0].title
                      if self.is_playlist and len(self.playlists) == 1 else
                      main_channel_json["snippet"]["title"].strip())
        auto_description = (clean_text(self.playlists[0].description)
                            if self.is_playlist and len(self.playlists) == 1
                            else clean_text(
                                main_channel_json["snippet"]["description"]))
        self.title = self.title or auto_title or "-"
        self.description = self.description or auto_description or "-"

        if self.creator is None:
            if self.is_single_channel:
                self.creator = _("Youtube Channel “{title}”").format(
                    title=main_channel_json["snippet"]["title"])
            else:
                self.creator = _("Youtube Channels")
        self.publisher = self.publisher or "Kiwix"

        self.tags = self.tags or ["youtube"]
        if "_videos:yes" not in self.tags:
            self.tags.append("_videos:yes")

        # copy our main_channel branding into /(profile|banner).jpg if not supplied
        if not self.profile_path.exists():
            shutil.copy(
                self.channels_dir.joinpath(self.main_channel_id,
                                           "profile.jpg"),
                self.profile_path,
            )
        if not self.banner_path.exists():
            shutil.copy(
                self.channels_dir.joinpath(self.main_channel_id, "banner.jpg"),
                self.banner_path,
            )

        # set colors from images if not supplied
        if self.main_color is None or self.secondary_color is None:
            profile_main, profile_secondary = get_colors(self.profile_path)
        self.main_color = self.main_color or profile_main
        self.secondary_color = self.secondary_color or profile_secondary

        resize_image(
            self.profile_path,
            width=48,
            height=48,
            method="thumbnail",
            dst=self.build_dir.joinpath("favicon.jpg"),
        )
Exemplo n.º 4
0
def test_resize_height(png_image, jpg_image, tmp_path, fmt):
    src, dst = get_src_dst(tmp_path,
                           fmt,
                           png_image=png_image,
                           jpg_image=jpg_image)

    width, height = 100, 50
    resize_image(src, width, height, dst=dst, method="height")
    _, th = get_image_size(dst)
    assert th == height
Exemplo n.º 5
0
def test_resize_width(png_image, jpg_image, tmp_path, fmt):
    src, dst = get_src_dst(tmp_path,
                           fmt,
                           png_image=png_image,
                           jpg_image=jpg_image)

    width, height = 100, 50
    resize_image(src, width, height, dst=dst, method="width")
    tw, _ = get_image_size(dst)
    assert tw == width
Exemplo n.º 6
0
def test_resize_upscale(png_image, jpg_image, tmp_path, fmt):
    src, dst = get_src_dst(tmp_path,
                           fmt,
                           png_image=png_image,
                           jpg_image=jpg_image)

    width, height = 500, 1000
    resize_image(src, width, height, dst=dst, method="cover")
    tw, th = get_image_size(dst)
    assert tw == width
    assert th == height
Exemplo n.º 7
0
def test_resize_contain(png_image, jpg_image, tmp_path, fmt):
    src, dst = get_src_dst(tmp_path,
                           fmt,
                           png_image=png_image,
                           jpg_image=jpg_image)

    width, height = 5, 50
    resize_image(src, width, height, dst=dst, method="contain")
    tw, th = get_image_size(dst)
    assert tw <= width
    assert th <= height
Exemplo n.º 8
0
def test_resize_thumbnail(png_image, jpg_image, tmp_path, fmt):
    src, dst = get_src_dst(tmp_path,
                           fmt,
                           png_image=png_image,
                           jpg_image=jpg_image)

    width, height = 100, 50
    resize_image(src, width, height, dst=dst, method="thumbnail")
    tw, th = get_image_size(dst)
    assert tw <= width
    assert th <= height
Exemplo n.º 9
0
    def check_branding_values(self):
        """checks that user-supplied images and colors are valid (so to fail early)

        Images are checked for existence or downloaded then resized
        Colors are check for validity"""

        # skip this step if none of related values were supplied
        if not sum([
                bool(x) for x in (
                    self.profile_image,
                    self.banner_image,
                    self.main_color,
                    self.secondary_color,
                )
        ]):
            return
        logger.info("checking your branding files and values")
        if self.profile_image:
            if self.profile_image.startswith("http"):
                stream_file(self.profile_image, self.profile_path)
            else:
                if not self.profile_image.exists():
                    raise IOError(
                        f"--profile image could not be found: {self.profile_image}"
                    )
                shutil.move(self.profile_image, self.profile_path)
            resize_image(self.profile_path,
                         width=100,
                         height=100,
                         method="thumbnail")
        if self.banner_image:
            if self.banner_image.startswith("http"):
                stream_file(self.banner_image, self.banner_path)
            else:
                if not self.banner_image.exists():
                    raise IOError(
                        f"--banner image could not be found: {self.banner_image}"
                    )
                shutil.move(self.banner_image, self.banner_path)
            resize_image(self.banner_path,
                         width=1060,
                         height=175,
                         method="thumbnail")

        if self.main_color and not is_hex_color(self.main_color):
            raise ValueError(
                f"--main-color is not a valid hex color: {self.main_color}")

        if self.secondary_color and not is_hex_color(self.secondary_color):
            raise ValueError(
                f"--secondary_color-color is not a valid hex color: {self.secondary_color}"
            )
Exemplo n.º 10
0
def test_resize_small_image_error(png_image, jpg_image, tmp_path, fmt):
    src, dst = get_src_dst(tmp_path,
                           fmt,
                           png_image=png_image,
                           jpg_image=jpg_image)

    width, height = 500, 1000
    with pytest.raises(ImageSizeError):
        resize_image(src,
                     width,
                     height,
                     dst=dst,
                     method="cover",
                     allow_upscaling=False)
Exemplo n.º 11
0
    def add_favicon(self):
        favicon_orig = self.build_dir / "favicon"
        # if user provided a custom favicon, retrieve that
        if self.favicon:
            handle_user_provided_file(source=self.favicon, dest=favicon_orig)
        # otherwise, get thumbnail from database
        else:
            # add channel thumbnail as favicon
            try:
                favicon_prefix, favicon_data = self.db.get_channel_metadata(
                    self.channel_id)["thumbnail"].split(";base64,", 1)
                favicon_data = base64.standard_b64decode(favicon_data)
                # favicon_mime = favicon_prefix.replace("data:", "")
                with open(favicon_orig, "wb") as fh:
                    fh.write(favicon_data)
                del favicon_data
            except Exception as exc:
                logger.warning(
                    "Unable to extract favicon from DB; using default")
                logger.exception(exc)

                # use a default favicon
                handle_user_provided_file(source=self.templates_dir /
                                          "kolibri-logo.png",
                                          dest=favicon_orig)

        # convert to PNG (might already be PNG but it's OK)
        favicon_fpath = favicon_orig.with_suffix(".png")
        convert_image(favicon_orig, favicon_fpath)

        # resize to appropriate size (ZIM uses 48x48 so we double for retina)
        for size in (96, 48):
            resize_image(favicon_fpath,
                         width=size,
                         height=size,
                         method="thumbnail")
            with open(favicon_fpath, "rb") as fh:
                self.creator.add_illustration(size, fh.read())

        # resize to appropriate size (ZIM uses 48x48)
        resize_image(favicon_fpath, width=96, height=96, method="thumbnail")

        # generate favicon
        favicon_ico_path = favicon_fpath.with_suffix(".ico")
        create_favicon(src=favicon_fpath, dst=favicon_ico_path)

        self.creator.add_item_for("favicon.png", fpath=favicon_fpath)
        self.creator.add_item_for("favicon.ico", fpath=favicon_ico_path)
Exemplo n.º 12
0
def test_resize_bytestream(png_image, jpg_image, tmp_path, fmt):
    src, dst = get_src_dst(tmp_path,
                           fmt,
                           png_image=png_image,
                           jpg_image=jpg_image)

    # copy image content into a bytes stream
    img = io.BytesIO()
    with open(src, "rb") as srch:
        img.write(srch.read())

    # resize in place (no dst)
    width, height = 100, 50
    resize_image(img, width, height, method="thumbnail")
    tw, th = get_image_size(img)
    assert tw <= width
    assert th <= height
Exemplo n.º 13
0
    def add_illustration(self, record=None):
        if self.favicon_url in self.indexed_urls:
            return

        # add illustration from favicon option or in-warc favicon
        logger.info("Adding illustration from " +
                    (self.favicon_url if record is None else "WARC"))
        favicon_fname = pathlib.Path(urlparse(self.favicon_url).path).name
        src_illus_fpath = pathlib.Path(".").joinpath(favicon_fname)

        # reusing payload from WARC record
        if record:
            with open(src_illus_fpath, "wb") as fh:
                if hasattr(record, "buffered_stream"):
                    record.buffered_stream.seek(0)
                    fh.write(record.buffered_stream.read())
                else:
                    fh.write(record.content_stream().read())
        # fetching online
        else:
            try:
                handle_user_provided_file(source=self.favicon_url,
                                          dest=src_illus_fpath)
            except Exception as exc:
                logger.warning(
                    "Unable to retrieve favicon. "
                    "ZIM won't have an illustration: {exc}".format(exc=exc))
                return

        # convert to PNG (might already be PNG but it's OK)
        illus_fpath = src_illus_fpath.with_suffix(".png")
        convert_image(src_illus_fpath, illus_fpath)

        # resize to appropriate size (ZIM uses 48x48 so we double for retina)
        for size in (96, 48):
            resize_image(illus_fpath,
                         width=size,
                         height=size,
                         method="thumbnail")
            with open(illus_fpath, "rb") as fh:
                self.creator.add_illustration(size, fh.read())
        src_illus_fpath.unlink()
Exemplo n.º 14
0
    def download_jpeg_image_and_convert(
        self, url, fpath, preset_options={}, resize=None
    ):
        """downloads a JPEG image and converts and optimizes it into desired format detected from fpath"""

        org_jpeg_path = pathlib.Path(
            tempfile.NamedTemporaryFile(delete=False, suffix=".jpg").name
        )
        save_large_file(url, org_jpeg_path)
        if resize is not None:
            resize_image(
                org_jpeg_path,
                width=resize[0],
                height=resize[1],
                method="cover",
            )
        optimize_image(
            org_jpeg_path, fpath, convert=True, delete_src=True, **preset_options
        )
        logger.debug(f"Converted {org_jpeg_path} to {fpath} and optimized ")
Exemplo n.º 15
0
def process_thumbnail(thumbnail_path, preset):
    # thumbnail might be WebP as .webp, JPEG as .jpg or WebP as .jpg
    tmp_thumbnail = thumbnail_path
    if not thumbnail_path.exists():
        logger.debug("We don't have video.webp, thumbnail is .jpg")
        tmp_thumbnail = thumbnail_path.with_suffix(".jpg")

    # resize thumbnail. we use max width:248x187px in listing
    # but our posters are 480x270px
    resize_image(
        tmp_thumbnail,
        width=480,
        height=270,
        method="cover",
        allow_upscaling=True,
    )
    optimize_image(tmp_thumbnail,
                   thumbnail_path,
                   delete_src=True,
                   **preset.options)
    return True
Exemplo n.º 16
0
 def get_image_data(self, url: str, **resize_args: dict) -> io.BytesIO:
     """Bytes stream of an optimized, resized WebP of the source image"""
     src, webp = io.BytesIO(), io.BytesIO()
     stream_file(url=url, byte_stream=src)
     with Image.open(src) as img:
         img.save(webp, format="WEBP")
     del src
     resize_args = resize_args or {}
     try:
         resize_image(
             src=webp,
             **resize_args,
             allow_upscaling=False,
         )
     except ImageSizeError as exc:
         logger.debug(f"Resize Error for {url}: {exc}")
     return optimize_webp(
         src=webp,
         lossless=False,
         quality=60,
         method=6,
     )