Compare commits

..

No commits in common. "8127242487e4b687d99c0f81b00d2e12b2a4a920" and "fb6d8308975d789b3fe2c7a54bc52cc921249a9c" have entirely different histories.

16 changed files with 59 additions and 142 deletions

View file

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

View file

@ -44,8 +44,8 @@ steps:
# commands: # commands:
# - buildah login -u $DOCKER_USER -p $DOCKER_PASS -- $DOCKER_REGISTRY # - buildah login -u $DOCKER_USER -p $DOCKER_PASS -- $DOCKER_REGISTRY
# - buildah manifest create ucast # - buildah manifest create ucast
# - 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 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 bud --tag code.thetadev.de/hsa/ucast:latest --manifest ucast --arch arm64 -f deploy/Dockerfile .
# - buildah manifest push --all ucast docker://code.thetadev.de/hsa/ucast:latest # - buildah manifest push --all ucast docker://code.thetadev.de/hsa/ucast:latest
# environment: # environment:
# DOCKER_REGISTRY: # DOCKER_REGISTRY:

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import json
import os import os
import re import re
from pathlib import Path from pathlib import Path
from typing import Any, Optional, Tuple, Union from typing import Any, Union
from urllib import parse from urllib import parse
import requests import requests
@ -12,6 +12,9 @@ import slugify
from django.utils import timezone from django.utils import timezone
from PIL import Image from PIL import Image
AVATAR_SM_WIDTH = 100
THUMBNAIL_SM_WIDTH = 360
EMOJI_PATTERN = re.compile( EMOJI_PATTERN = re.compile(
"[" "["
"\U0001F1E0-\U0001F1FF" # flags (iOS) "\U0001F1E0-\U0001F1FF" # flags (iOS)
@ -36,38 +39,13 @@ def download_file(url: str, download_path: Path):
open(download_path, "wb").write(r.content) open(download_path, "wb").write(r.content)
def resize_image(img: Image, resize: Tuple[int, int]): def download_image_file(url: str, download_path: Path):
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 Download an image and convert it to the type given
by the path. by the path.
:param url: Image URL :param url: Image URL
:param download_path: Download path :param download_path: Download path
:param resize: target image size (set to None for no resizing)
""" """
r = requests.get(url, allow_redirects=True) r = requests.get(url, allow_redirects=True)
r.raise_for_status() r.raise_for_status()
@ -77,15 +55,30 @@ def download_image_file(
if img_ext == "jpeg": if img_ext == "jpeg":
img_ext = "jpg" img_ext = "jpg"
if resize:
img = resize_image(img, resize)
if "." + img_ext == download_path.suffix: if "." + img_ext == download_path.suffix:
open(download_path, "wb").write(r.content) open(download_path, "wb").write(r.content)
else: else:
img.save(download_path) 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: def get_slug(text: str) -> str:
return slugify.slugify(text, lowercase=False, separator="_") return slugify.slugify(text, lowercase=False, separator="_")

View file

@ -2,12 +2,6 @@ from datetime import date
from pathlib import Path from pathlib import Path
from mutagen import id3 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( def tag_audio(
@ -32,21 +26,3 @@ def tag_audio(
encoding=3, mime="image/png", type=3, desc="Cover", data=albumart.read() encoding=3, mime="image/png", type=3, desc="Cover", data=albumart.read()
) )
tag.save() 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,7 +115,6 @@ def download_thumbnail(vinfo: VideoDetails, download_path: Path):
logging.info(f"downloading thumbnail {url}...") logging.info(f"downloading thumbnail {url}...")
try: try:
# util.download_image_file(url, download_path, videoutil.THUMBNAIL_SIZE)
util.download_image_file(url, download_path) util.download_image_file(url, download_path)
return return
except requests.HTTPError: except requests.HTTPError:

View file

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

View file

@ -2,7 +2,6 @@ import os
from django.db.models import ObjectDoesNotExist from django.db.models import ObjectDoesNotExist
from django.utils import timezone from django.utils import timezone
from PIL import Image
from ucast import queue from ucast import queue
from ucast.models import Channel, Video from ucast.models import Channel, Video
@ -52,32 +51,6 @@ def recreate_covers():
queue.enqueue(recreate_cover, video.id) 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(): def update_file_storage():
store = storage.Storage() store = storage.Storage()
@ -102,7 +75,7 @@ def update_file_storage():
return return
if not os.path.isfile(tn_file_sm): if not os.path.isfile(tn_file_sm):
videoutil.resize_thumbnail(tn_file, tn_file_sm) util.resize_thumbnail(tn_file, tn_file_sm)
if not os.path.isfile(cover_file): if not os.path.isfile(cover_file):
recreate_cover(video) recreate_cover(video)
@ -128,12 +101,8 @@ def update_channel_info(ch_id: int):
store = storage.Storage() store = storage.Storage()
channel_folder = store.get_or_create_channel_folder(channel.slug) channel_folder = store.get_or_create_channel_folder(channel.slug)
util.download_image_file( util.download_image_file(channel_data.avatar_url, channel_folder.file_avatar)
channel_data.avatar_url, channel_folder.file_avatar, videoutil.AVATAR_SIZE util.resize_avatar(channel_folder.file_avatar, channel_folder.file_avatar_sm)
)
videoutil.resize_avatar(
channel_folder.file_avatar, channel_folder.file_avatar_sm
)
channel.avatar_url = channel_data.avatar_url channel.avatar_url = channel_data.avatar_url

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

View file

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

View file

@ -55,22 +55,28 @@ def test_download_image_file_conv():
assert diff.getbbox() is None assert diff.getbbox() is None
@pytest.mark.parametrize( def test_resize_avatar():
"src_file", tmpdir_o = tempfile.TemporaryDirectory()
[ tmpdir = Path(tmpdir_o.name)
"normal", source_file = tests.DIR_TESTFILES / "avatar" / "a1.jpg"
"tall", resized_file = tmpdir / "avatar.webp"
"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))
normal_img = Image.open(tests.DIR_TESTFILES / "img" / "normal.png") util.resize_avatar(source_file, resized_file)
diff = ImageChops.difference(resized, normal_img)
assert diff.getbbox() is None 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)
@pytest.mark.parametrize( @pytest.mark.parametrize(

View file

@ -57,27 +57,3 @@ https://youtu.be/ZPxEr4YdWt8"""
expected_cover_img = Image.open(cover_file) expected_cover_img = Image.open(cover_file)
diff = ImageChops.difference(tag_cover_img, expected_cover_img) diff = ImageChops.difference(tag_cover_img, expected_cover_img)
assert diff.getbbox() is None 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)