diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..5507af8 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +source = src +omit = tests/*, __init__.py diff --git a/.gitignore b/.gitignore index af0af93..b3b495e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ __pycache__/ .idea/ app/ -*.log \ No newline at end of file +*/coverage/ +*.log +.coverage diff --git a/README.md b/README.md index 6d51a82..1359056 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,9 @@ Dies ist die dritte Version von AutoPicture. # Erste Schritte +# Tests + +```bash +sh ./tests/start_tests.sh +``` + diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..16d574f --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +pythonpath = tests +addopts = --ignore-glob=**/tests/** \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e2c5c44..899e52b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,7 @@ pillow python-magic progressbar virtualenv -requests \ No newline at end of file +requests +pytest==7.4.* +pytest-cov==4.1.* +pytest-factoryboy==2.5.* \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/exif_data.py b/src/exif_data.py index 1283552..be26a64 100644 --- a/src/exif_data.py +++ b/src/exif_data.py @@ -1,10 +1,10 @@ class ExifData: """This is for an object that stores the data of a picture""" - def __init__(self, image_path, image_name, day, month, year, time, make) -> None: - self.path = image_path - self.name = image_name - self.day = int(day) - self.month = int(month) + def __init__(self, image_path:str, image_name:str, day:int, month:int, year:int, time:str, make:str) -> None: + self.path:str = image_path + self.name:str = image_name + self.day:int = int(day) + self.month:int = int(month) self.year = int(year) self.time = str(time) self.make = str(make) diff --git a/src/file_handler.py b/src/file_handler.py index ca8ce52..beba1a4 100644 --- a/src/file_handler.py +++ b/src/file_handler.py @@ -1,10 +1,12 @@ import os +import sys +sys.path.append("../") import time import shutil import logging from progressbar.progressbar import ProgressBar -from exif_data import ExifData +from src.exif_data import ExifData def sort_pictures(images:list, dst:str, logger:logging.Logger): diff --git a/src/main.py b/src/main.py index 054c30c..e40638f 100644 --- a/src/main.py +++ b/src/main.py @@ -1,9 +1,9 @@ import sys import logging -from meta_data_handler import MetadataHandler +from meta_data_handler import get_meta_data from file_handler import sort_pictures -from folder_handler import * +from scan_folder import * sys.path.append("../") log_folder = "." @@ -16,12 +16,10 @@ handler = logging.FileHandler(filename=f'{log_folder}/AutoPicture.log', encoding handler.setFormatter(logging.Formatter('%(asctime)s|:%(message)s')) logger.addHandler(handler) -metadata_handler = MetadataHandler() - def start_process(): try: - exif_data = metadata_handler.get_meta_data(images=files) + exif_data = get_meta_data(images=files) sort_pictures(images=exif_data, dst=dst, logger=logger) except Exception as err: print(err) @@ -29,7 +27,7 @@ def start_process(): raise err -files = recursive_scan_dir(src) +files = recursive_scan_folder(src) if len(files) > 0: start_process() else: diff --git a/src/meta_data_handler.py b/src/meta_data_handler.py index da23939..72fb1ee 100644 --- a/src/meta_data_handler.py +++ b/src/meta_data_handler.py @@ -1,63 +1,66 @@ import magic +import sys + from PIL import Image from PIL import ExifTags -from exif_data import ExifData +sys.path.append("../") +from src.exif_data import ExifData + +video_formats = ["MP4", "MOV", "M4V", "MKV", "AVI", "WMV", "AVCHD", "WEBM", "MPEG"] +picture_formats = ["JPG", "JPEG", "PNG", "TIFF"] +key_words = ["DateTime", "Make"] -class MetadataHandler: - """This class is for getting the meta data from a image or a video""" +def is_file_video(path:str): + mime = magic.Magic(mime=True) + file = mime.from_file(path) + if file.find('video') != -1: return True + else: return False - def __init__(self) -> None: - self.__videoFormats = ["MP4", "MOV", "M4V", "MKV", "AVI", "WMV", "AVCHD", "WEBM", "MPEG"] - self.__pictureFormats = ["JPG", "JPEG", "PNG", "TIFF"] - self.__keyWords = ["DateTime", "Make"] - def __is_file_video(self, path:str): - mime = magic.Magic(mime=True) - file = mime.from_file(path) - if file.find('video') != -1: return True - else: return False +def get_image_meta_data(image_path): + image_extension = str(image_path).split("/")[-1].split(".") + # TODO: Sort out videos + img = Image.open(f"{image_path}") + values = [] + for tag, text in img.getexif().items(): + if tag in ExifTags.TAGS: + if image_extension[1].upper() in picture_formats: + values.append(ExifTags.TAGS[tag] + "|" + str(text)) + return filter_date_and_make(meta_tags=filter_data(value=values), image_path=image_path) - def __get_image_meta_data(self, image_path): - image_extension = str(image_path).split("/")[-1].split(".") - # TODO: Sort out videos - img = Image.open(f"{image_path}") - values = [] - for tag, text in img.getexif().items(): - if tag in ExifTags.TAGS: - if image_extension[1].upper() in self.__pictureFormats: - values.append(ExifTags.TAGS[tag] + "|" + str(text)) - return self.__filter_date_and_make(metaTags=self.__filter_data(value=values), imagePath=image_path) - def __filter_date_and_make(self, metaTags: list, imagePath): - day = None - month = None - year = None - time = None - make = str(metaTags[1]).split("|")[1] - image_name = str(imagePath).split("/")[-1] +def filter_date_and_make(meta_tags:list, image_path): + day = None + month = None + year = None + time = None + make = str(meta_tags[1]).split("|")[1] + image_name = str(image_path).split("/")[-1] - _date = str(metaTags[0]).split("|") - time = _date[1].split(" ")[1] - _date = _date[1].split(" ")[0].split(":") - day = _date[2] - month = _date[1] - year = _date[0] + _date = str(meta_tags[0]).split("|") + time = _date[1].split(" ")[1] + _date = _date[1].split(" ")[0].split(":") + day = int(_date[2]) + month = int(_date[1]) + year = int(_date[0]) - return ExifData(image_path=imagePath, image_name=image_name, day=day, month=month, year=year, time=time, make=make) + return ExifData(image_path=image_path, image_name=image_name, day=day, month=month, year=year, time=time, make=make) - def __filter_data(self, value): - value_return = [] - for k in self.__keyWords: - for v in value: - temp = v.split("|") - if temp[0] == k: - value_return.append(v) - return value_return - def get_meta_data(self, images: list): - exif_data_list = [] - for image in images: - exif_data_list.append(self.__get_image_meta_data(image_path=image)) - return exif_data_list +def filter_data(value): + value_return = [] + for k in key_words: + for v in value: + temp = v.split("|") + if temp[0] == k: + value_return.append(v) + return value_return + + +def get_meta_data(images: list): + exif_data_list = [] + for image in images: + exif_data_list.append(get_image_meta_data(image_path=image)) + return exif_data_list diff --git a/src/folder_handler.py b/src/scan_folder.py similarity index 82% rename from src/folder_handler.py rename to src/scan_folder.py index 9b16c8a..61af7b8 100644 --- a/src/folder_handler.py +++ b/src/scan_folder.py @@ -1,11 +1,11 @@ import os -def scan_dir(path:str): +def scan_folder(path:str): return next(os.walk(path))[2] -def recursive_scan_dir(path:str): +def recursive_scan_folder(path:str): results = [] for root, folders, files in os.walk(path): list_files = os.listdir(root) diff --git a/src/updater.py b/src/updater.py index e31aa29..f5a6d2d 100644 --- a/src/updater.py +++ b/src/updater.py @@ -2,17 +2,66 @@ import requests import json +class Version: + def __init__(self, data:dict): + self.version:str = data['version'] + self.version_int:int = int(self.version.replace(".", "")) + self.date:str = data['date'] + + +class Release: + def __init__(self, data:dict): + self.name = data['name'] + self.tag_name = data['tag_name'] + self.description = data['description'] + self.created_at = data['created_at'] + self.released_at = data['released_at'] + self.upcoming_release = data['upcoming_release'] + self.version_int = int(self.tag_name.replace(".", "")) + self.zip_file_url = data['assets']['sources'] + self.__proceed() + + def __proceed(self): + for assest in self.zip_file_url: + if assest['format'] == 'zip': + self.zip_file_url = assest['url'] + break + + def read_version(): with open(file=".version.json") as file: - version = json.load(file) + version = Version(json.load(file)) return version +def install(): + pass + + def check_for_update(): - request = "https://gitlab.com/DasMoorhuhn/autopicture-v3/-/raw/main/src/.version.json" - response = requests.get(request) + version_current = read_version() + request = "https://gitlab.com/api/v4/projects/52637155/releases" + response = requests.get(url=request, timeout=1) if not response.ok: return - print(response.text) + + # Get the latest release + releases_json = json.loads(response.text) + # index version + latest_release = [0, 0] + for release_json in releases_json: + release = Release(release_json) + if release.version_int > version_current.version_int: + latest_release[0] = releases_json.index(release_json) + latest_release[1] = release.version_int + + if latest_release == [0, 0]: return + release = Release(releases_json[latest_release[0]]) + print(f"v{version_current.version} -> v{release.tag_name}") + + if release.version_int > version_current.version_int: + # Update + print("Update") + print(release.zip_file_url) check_for_update() diff --git a/test.gitlab-ci.yml b/test.gitlab-ci.yml new file mode 100644 index 0000000..7e355f2 --- /dev/null +++ b/test.gitlab-ci.yml @@ -0,0 +1,30 @@ +prepare_job: + stage: prepare # This stage must run before the release stage + rules: + - if: $CI_COMMIT_TAG + when: never # Do not run this job when a tag is created manually + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch + script: + - echo "EXTRA_DESCRIPTION=some message" >> variables.env # Generate the EXTRA_DESCRIPTION and TAG environment variables + - echo "TAG=v$(cat VERSION)" >> variables.env # and append to the variables.env file + artifacts: + reports: + dotenv: variables.env # Use artifacts:reports:dotenv to expose the variables to other jobs + +release_job: + stage: release + image: registry.gitlab.com/gitlab-org/release-cli:latest + needs: + - job: prepare_job + artifacts: true + rules: + - if: $CI_COMMIT_TAG + when: never # Do not run this job when a tag is created manually + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch + script: + - echo "running release_job for $TAG" + release: + name: 'Release $TAG' + description: 'Created using the release-cli $EXTRA_DESCRIPTION' # $EXTRA_DESCRIPTION and the $TAG + tag_name: '$TAG' # variables must be defined elsewhere + ref: '$CI_COMMIT_SHA' # in the pipeline. For example, in the diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/helpers/__init__.py b/tests/helpers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/helpers/folder_helper.py b/tests/helpers/folder_helper.py new file mode 100644 index 0000000..7cd8521 --- /dev/null +++ b/tests/helpers/folder_helper.py @@ -0,0 +1,30 @@ +import os +import shutil +from pathlib import Path + +TEST_FOLDER = ".test_folder" +TEST_IMAGES = "tests/test_files" +TEST_TEMP_FOLDER = os.path.join(TEST_FOLDER, 'Temp') +TEST_IMAGE_FOLDER = os.path.join(TEST_FOLDER, 'Images') + + +def create_file(file): + Path(os.path.join(TEST_FOLDER, file)).touch() + + +def delete_folder(): + shutil.rmtree(TEST_FOLDER, ignore_errors=True) + + +def create_folders(): + delete_folder() + os.makedirs(os.path.join(TEST_FOLDER, '001', '001')) + os.makedirs(os.path.join(TEST_FOLDER, '002', '001')) + + +def copy_test_images(): + delete_folder() + os.makedirs(TEST_TEMP_FOLDER) + os.makedirs(TEST_IMAGE_FOLDER) + shutil.copy(src=os.path.join(TEST_IMAGES, 'test_image_001.JPG'), dst=TEST_TEMP_FOLDER) + shutil.copy(src=os.path.join(TEST_IMAGES, 'test_image_002.JPG'), dst=TEST_TEMP_FOLDER) diff --git a/tests/start_tests.sh b/tests/start_tests.sh new file mode 100755 index 0000000..7c64f2f --- /dev/null +++ b/tests/start_tests.sh @@ -0,0 +1 @@ +python3.10 -m pytest --no-header -rfp --cov --cov-report html:tests/coverage --cov-report xml:tests/coverage/coverage.xml tests/ \ No newline at end of file diff --git a/tests/test_exif_data.py b/tests/test_exif_data.py new file mode 100644 index 0000000..52593b1 --- /dev/null +++ b/tests/test_exif_data.py @@ -0,0 +1,15 @@ +from src.exif_data import ExifData +import unittest + + +class TestExifData(unittest.TestCase): + def test_exif_data(self): + exif_data = ExifData(image_path="/path/to/image", + image_name="Image.jpeg", + make="CAMERA", + month=12, + day=12, + year=2023, + time="10:10:10") + + assert exif_data.make == "CAMERA" diff --git a/tests/test_file_handle.py b/tests/test_file_handle.py new file mode 100644 index 0000000..07a54f4 --- /dev/null +++ b/tests/test_file_handle.py @@ -0,0 +1,25 @@ +import unittest +from unittest.mock import Mock + +from helpers.folder_helper import TEST_FOLDER +from helpers.folder_helper import TEST_IMAGE_FOLDER +from helpers.folder_helper import delete_folder +from helpers.folder_helper import copy_test_images + +from src.file_handler import sort_pictures +from src.scan_folder import recursive_scan_folder +from src.meta_data_handler import get_meta_data + + +class TestFileHandler(unittest.TestCase): + def test_file_handler(self): + copy_test_images() + files = recursive_scan_folder(TEST_FOLDER) + exif_data = get_meta_data(files) + sort_pictures(images=exif_data, logger=Mock(), dst=TEST_IMAGE_FOLDER) + sorted_pictures = recursive_scan_folder(TEST_FOLDER) + delete_folder() + + assert len(sorted_pictures) == 2 + assert "".join(picture for picture in sorted_pictures if '2023/10/25/test_image_002.JPG' in picture) != "" + assert "".join(picture for picture in sorted_pictures if '2023/10/28/test_image_001.JPG' in picture) != "" diff --git a/tests/test_files/test_image_001.JPG b/tests/test_files/test_image_001.JPG new file mode 100644 index 0000000..71ea400 Binary files /dev/null and b/tests/test_files/test_image_001.JPG differ diff --git a/tests/test_files/test_image_002.JPG b/tests/test_files/test_image_002.JPG new file mode 100644 index 0000000..11abc2c Binary files /dev/null and b/tests/test_files/test_image_002.JPG differ diff --git a/tests/test_meta_data_handler.py b/tests/test_meta_data_handler.py new file mode 100644 index 0000000..0335113 --- /dev/null +++ b/tests/test_meta_data_handler.py @@ -0,0 +1,23 @@ +import unittest +import os + +from src.meta_data_handler import get_meta_data +from src.meta_data_handler import get_image_meta_data +from src.meta_data_handler import filter_data +from src.meta_data_handler import filter_date_and_make +from src.meta_data_handler import is_file_video +from src.scan_folder import recursive_scan_folder + +TEST_IMAGES = "tests/test_files" + + +class TestMetadataHandler(unittest.TestCase): + def test_get_image_meta_data(self): + image_exif_data = get_image_meta_data(image_path=f"{TEST_IMAGES}/test_image_001.JPG") + assert image_exif_data.name == "test_image_001.JPG" + + def test_get_meta_data(self): + images = recursive_scan_folder(TEST_IMAGES) + print(images) + exif_data = get_meta_data(images) + assert len(exif_data) == len(images) diff --git a/tests/test_scan_dir.py b/tests/test_scan_dir.py new file mode 100644 index 0000000..295ed17 --- /dev/null +++ b/tests/test_scan_dir.py @@ -0,0 +1,46 @@ +import unittest + +from helpers.folder_helper import delete_folder +from helpers.folder_helper import create_folders +from helpers.folder_helper import create_file +from helpers.folder_helper import TEST_FOLDER + +from src.scan_folder import scan_folder +from src.scan_folder import recursive_scan_folder + + +class TestScanFolder(unittest.TestCase): + def test_scan_folder(self): + create_folders() + create_file(file='img_01.jpeg') + files = scan_folder(TEST_FOLDER) + delete_folder() + + assert len(files) == 1 + + def test_scan_empty_folder(self): + create_folders() + files = scan_folder(TEST_FOLDER) + delete_folder() + + assert len(files) == 0 + + def test_scan_recursive_folder(self): + create_folders() + create_file(file='img_01.jpeg') + create_file(file='img_02.jpeg') + create_file(file='001/img_03.jpeg') + create_file(file='001/001/img_04.jpeg') + create_file(file='002/001/img_05.jpeg') + + files = recursive_scan_folder(TEST_FOLDER) + delete_folder() + + assert len(files) == 5 + + def test_scan_recursive_empty_folder(self): + create_folders() + files = recursive_scan_folder(TEST_FOLDER) + delete_folder() + + assert len(files) == 0