Compare commits

...

2 commits

Author SHA1 Message Date
8127242487 Bump version: 0.4.1 → 0.4.2
All checks were successful
continuous-integration/drone/push Build is passing
2022-07-05 20:35:17 +02:00
9d41e6d5c3 add thumbnail resizing 2022-07-05 20:34:58 +02:00
16 changed files with 142 additions and 59 deletions

View file

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.4.1
current_version = 0.4.2
commit = True
tag = True

View file

@ -44,8 +44,8 @@ steps:
# commands:
# - buildah login -u $DOCKER_USER -p $DOCKER_PASS -- $DOCKER_REGISTRY
# - buildah manifest create ucast
# - buildah bud --tag code.thetadev.de/hsa/ucast:latest --manifest ucast --arch amd64 -f deploy/Dockerfile .
# - buildah bud --tag code.thetadev.de/hsa/ucast:latest --manifest ucast --arch arm64 -f deploy/Dockerfile .
# - buildah bud --tag code.thetadev.de/hsa/ucast:latest --manifest ucast --arch amd64 --build-arg TARGETPLATFORM=linux/amd64 -f deploy/Dockerfile .
# - buildah bud --tag code.thetadev.de/hsa/ucast:latest --manifest ucast --arch arm64 --build-arg TARGETPLATFORM=linux/arm64 -f deploy/Dockerfile .
# - buildah manifest push --all ucast docker://code.thetadev.de/hsa/ucast:latest
# environment:
# DOCKER_REGISTRY:

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "ucast"
version = "0.4.1"
version = "0.4.2"
description = "YouTube to Podcast converter"
authors = ["Theta-Dev <t.testboy@gmail.com>"]
packages = [

View file

@ -1,4 +1,4 @@
__version__ = "0.4.1"
__version__ = "0.4.2"
def template_context(request):

View file

@ -1,7 +1,7 @@
import shutil
from ucast.models import Channel, Video
from ucast.service import storage, util, youtube
from ucast.service import storage, util, videoutil, youtube
class ChannelAlreadyExistsException(Exception):
@ -12,8 +12,10 @@ class ChannelAlreadyExistsException(Exception):
def download_channel_avatar(channel: Channel):
store = storage.Storage()
channel_folder = store.get_or_create_channel_folder(channel.slug)
util.download_image_file(channel.avatar_url, channel_folder.file_avatar)
util.resize_avatar(channel_folder.file_avatar, channel_folder.file_avatar_sm)
util.download_image_file(
channel.avatar_url, channel_folder.file_avatar, videoutil.AVATAR_SIZE
)
videoutil.resize_avatar(channel_folder.file_avatar, channel_folder.file_avatar_sm)
def create_channel(channel_str: str) -> Channel:

View file

@ -4,7 +4,7 @@ import json
import os
import re
from pathlib import Path
from typing import Any, Union
from typing import Any, Optional, Tuple, Union
from urllib import parse
import requests
@ -12,9 +12,6 @@ import slugify
from django.utils import timezone
from PIL import Image
AVATAR_SM_WIDTH = 100
THUMBNAIL_SM_WIDTH = 360
EMOJI_PATTERN = re.compile(
"["
"\U0001F1E0-\U0001F1FF" # flags (iOS)
@ -39,13 +36,38 @@ def download_file(url: str, download_path: Path):
open(download_path, "wb").write(r.content)
def download_image_file(url: str, download_path: Path):
def resize_image(img: Image, resize: Tuple[int, int]):
if img.size == resize:
return img
w_ratio = resize[0] / img.width
h_ratio = resize[1] / img.height
box = None
# Too tall
if h_ratio < w_ratio:
crop_height = int(img.width / resize[0] * resize[1])
border = int((img.height - crop_height) / 2)
box = (0, border, img.width, img.height - border)
# Too wide
elif w_ratio < h_ratio:
crop_width = int(img.height / resize[1] * resize[0])
border = int((img.width - crop_width) / 2)
box = (border, 0, img.width - border, img.height)
return img.resize(resize, Image.Resampling.LANCZOS, box)
def download_image_file(
url: str, download_path: Path, resize: Optional[Tuple[int, int]] = None
):
"""
Download an image and convert it to the type given
by the path.
:param url: Image URL
:param download_path: Download path
:param resize: target image size (set to None for no resizing)
"""
r = requests.get(url, allow_redirects=True)
r.raise_for_status()
@ -55,30 +77,15 @@ def download_image_file(url: str, download_path: Path):
if img_ext == "jpeg":
img_ext = "jpg"
if resize:
img = resize_image(img, resize)
if "." + img_ext == download_path.suffix:
open(download_path, "wb").write(r.content)
else:
img.save(download_path)
def resize_avatar(original_file: Path, new_file: Path):
avatar = Image.open(original_file)
avatar_new_height = int(AVATAR_SM_WIDTH / avatar.width * avatar.height)
avatar = avatar.resize(
(AVATAR_SM_WIDTH, avatar_new_height), Image.Resampling.LANCZOS
)
avatar.save(new_file)
def resize_thumbnail(original_file: Path, new_file: Path):
thumbnail = Image.open(original_file)
tn_new_height = int(THUMBNAIL_SM_WIDTH / thumbnail.width * thumbnail.height)
thumbnail = thumbnail.resize(
(THUMBNAIL_SM_WIDTH, tn_new_height), Image.Resampling.LANCZOS
)
thumbnail.save(new_file)
def get_slug(text: str) -> str:
return slugify.slugify(text, lowercase=False, separator="_")

View file

@ -2,6 +2,12 @@ from datetime import date
from pathlib import Path
from mutagen import id3
from PIL import Image
AVATAR_SM_WIDTH = 100
THUMBNAIL_SM_WIDTH = 360
THUMBNAIL_SIZE = (1280, 720)
AVATAR_SIZE = (900, 900)
def tag_audio(
@ -26,3 +32,21 @@ def tag_audio(
encoding=3, mime="image/png", type=3, desc="Cover", data=albumart.read()
)
tag.save()
def resize_avatar(original_file: Path, new_file: Path):
avatar = Image.open(original_file)
avatar_new_height = int(AVATAR_SM_WIDTH / avatar.width * avatar.height)
avatar = avatar.resize(
(AVATAR_SM_WIDTH, avatar_new_height), Image.Resampling.LANCZOS
)
avatar.save(new_file)
def resize_thumbnail(original_file: Path, new_file: Path):
thumbnail = Image.open(original_file)
tn_new_height = int(THUMBNAIL_SM_WIDTH / thumbnail.width * thumbnail.height)
thumbnail = thumbnail.resize(
(THUMBNAIL_SM_WIDTH, tn_new_height), Image.Resampling.LANCZOS
)
thumbnail.save(new_file)

View file

@ -115,6 +115,7 @@ def download_thumbnail(vinfo: VideoDetails, download_path: Path):
logging.info(f"downloading thumbnail {url}...")
try:
# util.download_image_file(url, download_path, videoutil.THUMBNAIL_SIZE)
util.download_image_file(url, download_path)
return
except requests.HTTPError:

View file

@ -7,7 +7,7 @@ from yt_dlp.utils import DownloadError
from ucast import queue
from ucast.models import Channel, Video
from ucast.service import controller, cover, storage, util, videoutil, youtube
from ucast.service import controller, cover, storage, videoutil, youtube
def _load_scraped_video(vid: youtube.VideoScraped, channel: Channel):
@ -105,7 +105,7 @@ def download_video(v_id: int):
# Download/convert thumbnails
tn_path = channel_folder.get_thumbnail(video.slug)
youtube.download_thumbnail(details, tn_path)
util.resize_thumbnail(tn_path, channel_folder.get_thumbnail(video.slug, True))
videoutil.resize_thumbnail(tn_path, channel_folder.get_thumbnail(video.slug, True))
cover_file = channel_folder.get_cover(video.slug)
if not os.path.isfile(channel_folder.file_avatar):

View file

@ -2,6 +2,7 @@ import os
from django.db.models import ObjectDoesNotExist
from django.utils import timezone
from PIL import Image
from ucast import queue
from ucast.models import Channel, Video
@ -51,6 +52,32 @@ def recreate_covers():
queue.enqueue(recreate_cover, video.id)
def resize_thumbnail(v_id: int):
try:
video = Video.objects.get(id=v_id)
except ObjectDoesNotExist:
return
store = storage.Storage()
cf = store.get_channel_folder(video.channel.slug)
tn_path = cf.get_thumbnail(video.slug)
tn_img = Image.open(tn_path)
if tn_img.size != videoutil.THUMBNAIL_SIZE:
tn_img = util.resize_image(tn_img, videoutil.THUMBNAIL_SIZE)
tn_img.save(tn_path)
videoutil.resize_thumbnail(tn_path, cf.get_thumbnail(video.slug, True))
def resize_thumbnails():
"""
Used to unify thumbnail sizes for the existing collection before v0.4.2.
Needs to be triggered manually: ``manage.py rqenqueue ucast.tasks.library.resize_thumbnails``.
"""
for video in Video.objects.filter(downloaded__isnull=False):
queue.enqueue(resize_thumbnail, video.id)
def update_file_storage():
store = storage.Storage()
@ -75,7 +102,7 @@ def update_file_storage():
return
if not os.path.isfile(tn_file_sm):
util.resize_thumbnail(tn_file, tn_file_sm)
videoutil.resize_thumbnail(tn_file, tn_file_sm)
if not os.path.isfile(cover_file):
recreate_cover(video)
@ -101,8 +128,12 @@ def update_channel_info(ch_id: int):
store = storage.Storage()
channel_folder = store.get_or_create_channel_folder(channel.slug)
util.download_image_file(channel_data.avatar_url, channel_folder.file_avatar)
util.resize_avatar(channel_folder.file_avatar, channel_folder.file_avatar_sm)
util.download_image_file(
channel_data.avatar_url, channel_folder.file_avatar, videoutil.AVATAR_SIZE
)
videoutil.resize_avatar(
channel_folder.file_avatar, channel_folder.file_avatar_sm
)
channel.avatar_url = channel_data.avatar_url

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

View file

@ -48,7 +48,7 @@ def _create_download_dir() -> Tuple[Path, TemporaryDirectory]:
shutil.copyfile(
tests.DIR_TESTFILES / "avatar" / f"{avatar}.jpg", cf.file_avatar
)
util.resize_avatar(cf.file_avatar, cf.file_avatar_sm)
videoutil.resize_avatar(cf.file_avatar, cf.file_avatar_sm)
return tmpdir, tmpdir_o
@ -75,7 +75,7 @@ def _add_download_dir_content():
shutil.copyfile(tests.DIR_TESTFILES / "audio" / "audio1.mp3", file_audio)
shutil.copyfile(tests.DIR_TESTFILES / "thumbnail" / f"{vid}.webp", file_tn)
util.resize_thumbnail(file_tn, cf.get_thumbnail(video_slug, True))
videoutil.resize_thumbnail(file_tn, cf.get_thumbnail(video_slug, True))
cover.create_cover_file(
file_tn,
cf.file_avatar,

View file

@ -55,28 +55,22 @@ def test_download_image_file_conv():
assert diff.getbbox() is None
def test_resize_avatar():
tmpdir_o = tempfile.TemporaryDirectory()
tmpdir = Path(tmpdir_o.name)
source_file = tests.DIR_TESTFILES / "avatar" / "a1.jpg"
resized_file = tmpdir / "avatar.webp"
@pytest.mark.parametrize(
"src_file",
[
"normal",
"tall",
"wide",
],
)
def test_resize_image(src_file: str):
src_path = tests.DIR_TESTFILES / "img" / f"{src_file}.png"
src_img = Image.open(src_path)
resized = util.resize_image(src_img, (500, 250))
util.resize_avatar(source_file, resized_file)
resized_avatar = Image.open(resized_file)
assert resized_avatar.size == (100, 100)
def test_resize_thumbnail():
tmpdir_o = tempfile.TemporaryDirectory()
tmpdir = Path(tmpdir_o.name)
source_file = tests.DIR_TESTFILES / "thumbnail" / "t1.webp"
resized_file = tmpdir / "thumbnail.webp"
util.resize_thumbnail(source_file, resized_file)
resized_thumbnail = Image.open(resized_file)
assert resized_thumbnail.size == (360, 202)
normal_img = Image.open(tests.DIR_TESTFILES / "img" / "normal.png")
diff = ImageChops.difference(resized, normal_img)
assert diff.getbbox() is None
@pytest.mark.parametrize(

View file

@ -57,3 +57,27 @@ https://youtu.be/ZPxEr4YdWt8"""
expected_cover_img = Image.open(cover_file)
diff = ImageChops.difference(tag_cover_img, expected_cover_img)
assert diff.getbbox() is None
def test_resize_avatar():
tmpdir_o = tempfile.TemporaryDirectory()
tmpdir = Path(tmpdir_o.name)
source_file = tests.DIR_TESTFILES / "avatar" / "a1.jpg"
resized_file = tmpdir / "avatar.webp"
videoutil.resize_avatar(source_file, resized_file)
resized_avatar = Image.open(resized_file)
assert resized_avatar.size == (100, 100)
def test_resize_thumbnail():
tmpdir_o = tempfile.TemporaryDirectory()
tmpdir = Path(tmpdir_o.name)
source_file = tests.DIR_TESTFILES / "thumbnail" / "t1.webp"
resized_file = tmpdir / "thumbnail.webp"
videoutil.resize_thumbnail(source_file, resized_file)
resized_thumbnail = Image.open(resized_file)
assert resized_thumbnail.size == (360, 202)