Compare commits

..

No commits in common. "e1315743931f43f9649f4f46b54d54cd95e8f783" and "c6c3849a826c2f9afc71c78c1ffa212f836bec23" have entirely different histories.

9 changed files with 23 additions and 137 deletions

View file

@ -9,7 +9,7 @@ _ data
|_ LinusTechTips
|_ .ucast
|_ videos.json # IDs und Metadaten aller heruntergeladenen Videos
|_ options.json # Kanalspezifische Optionen (ID, enabled)
|_ options.json # Kanalspezifische Optionen (ID, LastScan)
|_ avatar.png # Profilbild des Kanals
|_ feed.xml # RSS-Feed
|_ covers # Cover-Bilder
@ -24,10 +24,15 @@ _ data
## Datenmodelle
### LastScan
- LastScan: datetime
### ChannelOptions
- ID: str
- Active: bool = True
- LastScan: datetime
- SkipLivestreams: bool = True
- SkipShorts: bool = True
- KeepVideos: int = -1
@ -38,7 +43,6 @@ _ data
### Video
- ID: str
- Title: str
- Slug: str (YYMMDD_Title, used as filename)
- Published: datetime

14
poetry.lock generated
View file

@ -174,14 +174,6 @@ category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "invoke"
version = "1.7.0"
description = "Pythonic task execution"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "itsdangerous"
version = "2.1.2"
@ -447,7 +439,7 @@ websockets = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "cf8899258dac046f0ed3d0492161db330ab735dc8dcbe1c46d2c8d4e48b66342"
content-hash = "df5be5b98bd03da41732b908331f0a731408f65a96b078c0139773f0759afac3"
[metadata.files]
atomicwrites = [
@ -695,10 +687,6 @@ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
invoke = [
{file = "invoke-1.7.0-py3-none-any.whl", hash = "sha256:a5159fc63dba6ca2a87a1e33d282b99cea69711b03c64a35bb4e1c53c6c4afa0"},
{file = "invoke-1.7.0.tar.gz", hash = "sha256:e332e49de40463f2016315f51df42313855772be86435686156bc18f45b5cc6c"},
]
itsdangerous = [
{file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
{file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},

View file

@ -20,7 +20,6 @@ fonts = "^0.0.3"
[tool.poetry.dev-dependencies]
pytest = "^7.1.1"
pytest-cov = "^3.0.0"
invoke = "^1.7.0"
[build-system]
requires = ["poetry-core>=1.0.0"]

View file

@ -1,14 +1,12 @@
# coding=utf-8
from typing import List
import tempfile
from pathlib import Path
import pytest
from PIL import Image, ImageFont, ImageChops
from PIL import ImageFont
from fonts.ttf import SourceSansPro
import tests
from ucast import cover, types
from ucast import cover
@pytest.mark.parametrize('height,width,text,expect', [
@ -24,61 +22,3 @@ def test_split_text(height: int, width: int, text: str, expect: List[str]):
font = ImageFont.truetype(SourceSansPro, 40)
lines = cover._split_text(height, width, text, font, 8)
assert lines == expect
@pytest.mark.parametrize('file_name,color', [
('t1.webp', (63, 63, 62)),
('t2.webp', (74, 45, 37)),
('t3.webp', (54, 24, 28)),
])
def test_get_dominant_color(file_name: str, color: types.Color):
img = Image.open(tests.DIR_TESTFILES / 'thumbnail' / file_name)
c = cover._get_dominant_color(img)
assert c == color
@pytest.mark.parametrize('bg_color,text_color', [
((100, 0, 0), (255, 255, 255)),
((200, 200, 0), (0, 0, 0)),
])
def test_get_text_color(bg_color: types.Color, text_color: types.Color):
c = cover._get_text_color(bg_color)
assert c == text_color
@pytest.mark.parametrize('n_image,title,channel', [
(1, 'ThetaDev @ Embedded World 2019', 'ThetaDev'),
(2, 'Sintel - Open Movie by Blender Foundation', 'Blender'),
(3, 'Systemabsturz Teaser zur DiVOC bb3', 'media.ccc.de'),
])
def test_create_cover_image(n_image: int, title: str, channel: str):
tn_file = tests.DIR_TESTFILES / 'thumbnail' / f't{n_image}.webp'
av_file = tests.DIR_TESTFILES / 'avatar' / f'a{n_image}.jpg'
expected_cv_file = tests.DIR_TESTFILES / 'cover' / f'c{n_image}.png'
tn_image = Image.open(tn_file)
av_image = Image.open(av_file)
expected_cv_image = Image.open(expected_cv_file)
cv_image = cover._create_cover_image(tn_image, av_image, title, channel)
diff = ImageChops.difference(cv_image, expected_cv_image)
assert diff.getbbox() is None
def test_create_cover_file():
tn_file = tests.DIR_TESTFILES / 'thumbnail' / 't1.webp'
av_file = tests.DIR_TESTFILES / 'avatar' / 'a1.jpg'
expected_cv_file = tests.DIR_TESTFILES / 'cover' / 'c1.png'
tmpdir_o = tempfile.TemporaryDirectory()
tmpdir = Path(tmpdir_o.name)
cv_file = tmpdir / 'cover.png'
cover.create_cover_file(tn_file, av_file, 'ThetaDev @ Embedded World 2019', 'ThetaDev', cv_file)
cv_image = Image.open(cv_file)
expected_cv_image = Image.open(expected_cv_file)
diff = ImageChops.difference(cv_image, expected_cv_image)
assert diff.getbbox() is None

View file

@ -1,19 +1,21 @@
# coding=utf-8
import sys
import os
from invoke import task
from ucast import youtube, util, cover
import tests
@task
def test(c):
c.run('pytest tests', pty=True)
# Mit diesem Skript kann man Coverbilder zum Testen erzeugen
# python tests/testfiles/get_cover.py <Video-ID>
@task
def get_cover(c, vid=''):
vinfo = youtube.get_video_info(vid)
if __name__ == '__main__':
if len(sys.argv) <= 1:
print('No video id given')
sys.exit(1)
video_id = sys.argv[1]
vinfo = youtube.get_video_info(video_id)
title = vinfo['fulltitle']
channel_name = vinfo['uploader']
thumbnail_url = youtube.get_thumbnail_url(vinfo)

View file

@ -1,6 +1,5 @@
# coding=utf-8
import math
from pathlib import Path
from typing import Tuple, List, Optional
from PIL import Image, ImageDraw, ImageFont
@ -160,8 +159,8 @@ def _create_cover_image(thumbnail: Image.Image, avatar: Optional[Image.Image], t
return cover
def create_cover_file(thumbnail_path: Path, avatar_path: Optional[Path], title: str, channel: str,
cover_path: Path):
def create_cover_file(thumbnail_path: types.Path, avatar_path: Optional[types.Path], title: str, channel: str,
cover_path: types.Path):
thumbnail = Image.open(thumbnail_path)
avatar = None

View file

@ -1,3 +0,0 @@
# coding=utf-8

View file

@ -1,44 +0,0 @@
# coding=utf-8
import os
from pathlib import Path
UCAST_DIRNAME = '.ucast'
class ChannelFolder:
def __init__(self, dir_root: Path):
self.dir_root = dir_root
dir_ucast = self.dir_root / UCAST_DIRNAME
self.file_videos = dir_ucast / 'videos.json'
self.file_options = dir_ucast / 'options.json'
self.file_avatar = dir_ucast / 'avatar.png'
self.file_feed = dir_ucast / 'feed.xml'
self.dir_covers = dir_ucast / 'covers'
def does_exist(self) -> bool:
return os.path.isdir(self.dir_covers)
def create(self):
os.makedirs(self.dir_covers, exist_ok=True)
class Storage:
def __init__(self, config_dir: Path, data_dir: Path):
self.dir_config = config_dir
self.dir_data = data_dir
def get_channel_folder(self, channel_name: str):
cf = ChannelFolder(self.dir_data / channel_name)
if not cf.does_exist():
raise FileNotFoundError('channel folder does not exist')
return cf
def create_channel_folder(self, channel_name: str):
cf = ChannelFolder(self.dir_data / channel_name)
if cf.does_exist():
raise FileExistsError('channel folder already exists')
cf.create()
return cf

View file

@ -3,3 +3,4 @@ from os import PathLike
from typing import Tuple, Union
Color = Tuple[int, int, int]
Path = Union[str, bytes, PathLike]