46 Commits
0.1.0 ... main

Author SHA1 Message Date
c283e0221a Merge branch 'develop' into 'main'
develop

See merge request DasMoorhuhn/autopicture-v3!7
2024-05-25 21:18:24 +00:00
c051e120fe changelog and version update 2024-05-25 23:16:38 +02:00
cc3f0ea89b Fix CI 2024-05-25 23:04:05 +02:00
161525fbb5 i18n, tests and more 2024-05-25 22:42:29 +02:00
c21167f941 mime types 2024-01-13 20:06:04 +01:00
892df0144e Merge remote-tracking branch 'origin/main' into develop 2023-12-21 14:23:04 +01:00
aaf7060252 added new file for development modules 2023-12-21 14:22:09 +01:00
0660628647 Modified README 2023-12-21 14:02:48 +01:00
e6c59507c4 wqadded new way to get mime type 2023-12-21 13:54:24 +01:00
f8383567fe changed to dict 2023-12-21 11:27:29 +01:00
a869b60933 Merge branch 'develop' into 'main'
develop

See merge request DasMoorhuhn/autopicture-v3!6
2023-12-17 20:37:28 +00:00
3c8974ee74 activate apple test 2023-12-17 21:36:32 +01:00
cdf8948193 did more tests 2023-12-17 21:15:06 +01:00
f2fd803b57 yeet 2023-12-17 19:31:35 +01:00
033eb269f0 Merge branch 'develop' into 'main'
Develop

See merge request DasMoorhuhn/autopicture-v3!5
2023-12-15 00:12:53 +00:00
480b97059d made tests run 2023-12-15 01:01:29 +01:00
e5ed2e7319 raise to python3.12 2023-12-15 01:00:31 +01:00
0f22b70e06 fixture versions 2023-12-12 22:56:03 +01:00
ffdf3f3779 added more unit tests 2023-12-12 22:37:15 +01:00
4d8c0c1dda added testfiles for iphone 2023-12-12 22:26:28 +01:00
6bea0ee524 seperated main 2023-12-12 21:56:30 +01:00
5d7ca9172b raise to python3.11 2023-12-12 21:22:21 +01:00
0f6601ecd9 removed round 2023-12-12 03:36:01 +01:00
f32822c861 ci only on main 2023-12-12 03:33:15 +01:00
52b776d495 Merge branch 'develop' into 'main'
try coverage

See merge request DasMoorhuhn/autopicture-v3!4
2023-12-12 02:31:17 +00:00
941aa141af try coverage 2023-12-12 03:30:40 +01:00
3e0a40eee9 fix coverage 2023-12-12 02:36:15 +01:00
c774b8428c fix coverage 2023-12-12 02:34:09 +01:00
d440f3ff1d .. 2023-12-12 02:31:41 +01:00
6b4e8df4a5 .. 2023-12-12 02:20:46 +01:00
a5160aa0c9 .. 2023-12-12 02:19:08 +01:00
250f7b6554 .. 2023-12-12 02:14:46 +01:00
d67b6f5669 Merge branch 'develop' into 'main'
Develop

See merge request DasMoorhuhn/autopicture-v3!3
2023-12-12 00:46:51 +00:00
968c549bbe CI 2023-12-12 01:46:19 +01:00
0cf66c5781 added test files 2023-12-12 00:35:36 +01:00
fc9ce09e0f .. 2023-12-08 01:19:00 +01:00
c6bda88974 excluded updater because not needed atm 2023-12-08 01:17:16 +01:00
4544681f45 Merge branch 'develop' into 'main'
Develop

See merge request DasMoorhuhn/autopicture-v3!2
2023-12-08 00:14:49 +00:00
27c5cd5076 more test stuff 2023-12-08 01:14:01 +01:00
0012c52adf 54 percent... wow 2023-12-08 00:44:12 +01:00
9ae71af1aa more tests 2023-12-07 22:27:05 +01:00
3ebe5bba7f fixed test call 2023-12-07 22:12:56 +01:00
529776bac6 start making tests 2023-12-07 21:58:42 +01:00
d9ac0072ac included all files 2023-12-07 20:38:01 +01:00
2da422abc5 make pytests 2023-12-07 19:59:51 +01:00
cb9e02f4a2 added tests and scan for releases 2023-12-01 01:26:38 +01:00
56 changed files with 728 additions and 101 deletions

3
.coveragerc Normal file
View File

@@ -0,0 +1,3 @@
[run]
source = src
omit = tests/*, __init__.py, updater.py, main.py

5
.gitignore vendored
View File

@@ -1,4 +1,9 @@
__pycache__/
.idea/
app/
.test_folder/
*/coverage/
*.log
*.xml
.coverage

13
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,13 @@
pytest:
image: python:3.12-alpine
script:
- sh tests/start_tests_gitlab.sh
# - sed -i "s#<source>/builds/DasMoorhuhn/autopicture-v3/src</source>#<source>${CI_PROJECT_DIR}</source>#g" coverage.xml
coverage: '/Code coverage: \d+(?:\.\d+)?/'
artifacts:
name: "$CI_JOB_NAME"
reports:
junit: report.xml
coverage_report:
coverage_format: cobertura
path: coverage.xml

View File

@@ -1,3 +1,10 @@
## v0.2.0 []
- Added i18n for multi language support
- Fixed tests
- Fixed gitlab CI file
- Fixed bug on string splitting at filtering date/time
- Only pictures will be sorted
- WIP: RAW sorting
## v0.1.0 [2023-11-30]
- Added updater
- Added Changelog file

View File

@@ -1,6 +1,75 @@
# AutoPicture V3
Dies ist die dritte Version von AutoPicture.
Bildersortierprogramm geschrieben in Python3.12.
Example structure:
```bash
app/Bilder/
└── SONY
└── 2023
├── 10
│   ├── 21
│   │   ├── DSC02975.JPG
│   │   └── DSC02976.JPG
│   ├── 25
│   │   ├── DSC03030.JPG
│   │   ├── DSC03031.JPG
│   │   └── DSC03096.JPG
│   ├── 28
│   │   ├── DSC03097.JPG
│   │   ├── DSC03098.JPG
│   │   └── DSC03116.JPG
│   ├── 29
│   │   ├── DSC03117.JPG
│   │   ├── DSC03118.JPG
│   │   └── DSC03135.JPG
│   └── 30
│   ├── DSC03136.JPG
│   └── DSC03137.JPG
├── 11
│   ├── 16
│   │   └── DSC03144.JPG
│   ├── 17
│   │   ├── DSC03145.JPG
│   │   └── DSC03146.JPG
│   └── 28
│   ├── DSC03153.JPG
│   ├── DSC03154.JPG
│   └── DSC03155.JPG
├── ...
```
# Erste Schritte
## Python
### Linux
Debian:
```bash
sudo apt-get install -y python3 python3-pip
```
### Windows
TODO:
Download the latest version of Python and install it
## Pip
```bash
pip3 install -r requirements.txt
```
## Config
## Starten
# Tests
```bash
sh ./tests/start_tests.sh
```

4
develop_requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
pytest
pytest-cov
pytest-factoryboy
gitlabci-local

1
install.sh Normal file
View File

@@ -0,0 +1 @@
sudo apt-get install -y python3 python3-pip

3
pytest.ini Normal file
View File

@@ -0,0 +1,3 @@
[pytest]
pythonpath = tests
addopts = --ignore-glob=**/tests/**

View File

@@ -1,5 +1,8 @@
pillow
pyyaml
python-magic
progressbar
virtualenv
requests
filetype
python-i18n[YAML]

View File

@@ -1,4 +1,4 @@
{
"version": "0.1.0",
"date": "2023-11-30"
"version": "0.2.0",
"date": "2024-05-25"
}

0
src/__init__.py Normal file
View File

17
src/config.py Normal file
View File

@@ -0,0 +1,17 @@
import yaml
import os
workdir, filename = os.path.split(os.path.abspath(__file__))
config_file = f"{workdir}{os.sep}config.yml"
def get_config():
with open(file=config_file, mode='r') as file:
return Config(yaml.safe_load(file))
class Config:
def __init__(self, data):
self.src = data['src']
self.dst = data['dst']
self.language = data['language']

7
src/config.yml Normal file
View File

@@ -0,0 +1,7 @@
# src (source) is the path, were the images are, that should be sorted
src: "../app/Temp"
# dst (destination) ist the path, were the images are going to be sorted
dst: "../app/Bilder"
language: "en"

View File

@@ -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)
self.year = int(year)
self.time = str(time)
self.make = str(make)
def __init__(self, data:dict) -> None:
self.path:str = str(data['image_path'])
self.name:str = str(data['image_name'])
self.day:int = int(data['day'])
self.month:int = int(data['month'])
self.year:int = int(data['year'])
self.time = data['time']
self.make:str = str(data['make'])

View File

@@ -1,37 +1,40 @@
import os
import time
import sys
sys.path.append("../")
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):
image_total = len(images)
image_counter = 0
logging_infos = []
progress_bar = ProgressBar(
maxval=image_total,
term_width=70
)
print(f"Start sorting {image_total} images\n")
progress_bar.start()
start_timer = time.time()
for image in images:
image:ExifData
if not image: continue
path = os.path.join(dst, str(image.make), str(image.year), str(image.month), str(image.day))
if not os.path.exists(path): os.makedirs(path)
stat_info = os.stat(image.path)
shutil.move(src=image.path, dst=f"{path}/{image.name}")
image_dst = f"{path}{os.sep}{image.name}"
shutil.move(src=image.path, dst=image_dst)
# os.chmod(path=f"{path}/{image.name}", mode=stat_info.st_mode)
logger.info(f"Moved {image.path} -> {path}/{image.name}")
logger.info(f"Moved {image.path} -> {image_dst}")
progress_bar.update(image_counter)
image_counter += 1
end_timer = time.time()
progress_bar.finish()
print(f"\nDone\nSorted {image_total} images in {round(end_timer - start_timer, 2)} seconds")
def sort_raws(raws:list, dst:str, logger:logging):
pass

View File

@@ -0,0 +1,5 @@
de:
start_sorting_images: 'Sortierung von %{image_count} Bild(ern) wird gestartet'
done_sorting_images: '%{image_count} Bild(er) in %{time} Sekunden sortiert'
done: 'Fertig'
no_images_found: 'Es wurden keine Bilder gefunden'

View File

@@ -0,0 +1,5 @@
en:
start_sorting_images: 'Start sorting %{image_count} image(s)\n'
done_sorting_images: 'Sorted %{image_count} image(s) in %{time} seconds'
done: 'Done'
no_images_found: 'No images found'

View File

@@ -1,14 +1,9 @@
import sys
import logging
from meta_data_handler import MetadataHandler
from file_handler import sort_pictures
from folder_handler import *
from process import start_process
sys.path.append("../")
log_folder = "."
src = "../app/TempPic"
dst = "../app/Bilder"
logger = logging.getLogger('AutoPicture')
logger.setLevel(logging.DEBUG)
@@ -16,21 +11,4 @@ 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)
sort_pictures(images=exif_data, dst=dst, logger=logger)
except Exception as err:
print(err)
logger.error(err)
raise err
files = recursive_scan_dir(src)
if len(files) > 0:
start_process()
else:
print("No images found")
start_process(logger=logger)

View File

@@ -1,63 +1,97 @@
import magic
import sys
import os
import filetype
from PIL import Image
from PIL import ExifTags
from exif_data import ExifData
sys.path.append("../")
from src.exif_data import ExifData
from src.mime_types import MimeTypes
video_formats = ["MP4", "MOV", "M4V", "MKV", "AVI", "WMV", "AVCHD", "WEBM", "MPEG"]
picture_formats = ["JPG", "JPEG", "PNG"]
raw_formats = ["CR2", "RAF", "RW2", "ERF", "NRW", "NEF", "ARW", "RWZ", "EIP",
"DNG", "BAY", "DCR", "GPR", "RAW", "CRW", "3FR", "SR2", "K25",
"KC2", "MEF", "DNG", "CS1", "ORF", "MOS", "KDC", "CR3", "ARI",
"SRF", "SRW", "J6I", "FFF", "MRW", "MFW", "RWL", "X3F", "PEF",
"IIQ", "CXI", "NKSC", "MDC"]
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 is_file_picture(path:str):
mime = magic.Magic(mime=True)
file = mime.from_file(path)
if file.find('picture') != -1: return True
else: return False
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 handle_raw(image:str):
image_creation_time = os.path.getctime(filename=image)
print(image_creation_time)
_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]
return ExifData(image_path=imagePath, image_name=image_name, day=day, month=month, year=year, time=time, make=make)
def handle_image(image:str):
img = Image.open(image)
values = []
for tag, text in img.getexif().items():
if tag in ExifTags.TAGS: values.append([ExifTags.TAGS[tag], str(text)])
return filter_date_and_make(meta_tags=filter_data(values=values), image_path=image)
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 handle_video(video:str):
pass
def get_image_meta_data(image_path):
image_extension = str(image_path).split("/")[-1].split(".")[1].upper()
# TODO: Sort out videos using mime type of file
# mime = MimeTypes(file_path=image_path)
if image_extension in picture_formats: return handle_image(image=image_path)
elif image_extension in video_formats: return handle_video(video=image_path)
elif image_extension in raw_formats: return handle_raw(image=image_path)
def filter_date_and_make(meta_tags:list, image_path):
make = meta_tags[0][1]
image_name = str(image_path).split("/")[-1]
date_time = meta_tags[1][1].split(" ")
date, time = date_time[0].split(":"), date_time[1]
year, month, day = date[0], date[1], date[2]
exif_data_dict = {
"day": day,
"month": month,
"year": year,
"make": make,
"time": time,
"image_path": image_path,
"image_name": image_name
}
return ExifData(exif_data_dict)
def filter_data(values):
"""Filters the data according to the meta tags from the keyword list"""
value_return = []
for value in values:
value_return.append(value) if value[0] in key_words else {}
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

18
src/mime_types.py Normal file
View File

@@ -0,0 +1,18 @@
import filetype
class MimeTypes:
def __init__(self, file_path):
self.is_image = False
self.is_video = False
self.is_raw = False
self.is_unsupported_file_type = False
self.__proceed(file_path)
def __proceed(self, file_path):
if filetype.is_image(file_path):
self.is_image = True
elif filetype.is_video(file_path):
self.is_video = True
else:
self.is_unsupported_file_type = True

43
src/process.py Normal file
View File

@@ -0,0 +1,43 @@
import os
import sys
import i18n
sys.path.append("../")
from time import time
from src.meta_data_handler import get_meta_data
from src.file_handler import sort_pictures
from src.scan_folder import recursive_scan_folder
from src.config import get_config
workdir, filename = os.path.split(os.path.abspath(__file__))
config = get_config()
i18n.set('locale', config.language)
i18n.set('fallback', 'en')
i18n.set('filename_format', '{locale}.{format}')
i18n.load_path.append(f'{workdir}{os.sep}i18n_translations{os.sep}')
def start_process(logger):
try:
files = recursive_scan_folder(config.src)
if len(files) > 0:
start_timer = time()
exif_data = get_meta_data(images=files)
image_total = len(exif_data)
print(i18n.t('start_sorting_images', image_count=image_total))
sort_pictures(images=exif_data, dst=config.dst, logger=logger)
end_timer = time()
duration = round(end_timer - start_timer, 2)
print(i18n.t('done'))
print(i18n.t('done_sorting_images', time=duration, image_count=image_total))
return True
else:
print(i18n.t('no_images_found'))
return False
except Exception as err:
print(err)
logger.error(err)
raise err

View File

@@ -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)

View File

@@ -1,18 +1,69 @@
import requests
import json
# Proof of concept
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()

30
test.gitlab-ci.yml Normal file
View File

@@ -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

0
tests/__init__.py Normal file
View File

17
tests/edit_source.py Normal file
View File

@@ -0,0 +1,17 @@
with open(file="coverage.xml") as file:
file_content = file.readlines()
for line in file_content:
try:
orig_line = file_content.index(line)
line = line.strip().split("<")[1].split(">")
if line[0] == "source":
print("FOUND")
file_content[orig_line] = "<source>src</source>\n"
except:
pass
with open(file="coverage.xml", mode="w") as file:
file.writelines(file_content)

View File

@@ -0,0 +1,14 @@
with open(file="coverage.xml") as file:
file_content = file.readlines()
line_rate = None
for line in file_content:
try:
line = line.strip().split("<")[1].split(">")[0].split(" ")
if line[0] == "package":
line_rate = "Code coverage: " + str(float(line[2].split("=\"")[1].split("\"")[0])*100) + "%"
except:
pass
print(line_rate)

View File

View File

@@ -0,0 +1,44 @@
import os
import shutil
from pathlib import Path
from src.scan_folder import recursive_scan_folder
TEST_FOLDER = ".test_folder"
TEST_IMAGES = f"tests{os.sep}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)
def copy_images(brand:str, model:str):
delete_folder()
create_folders()
files = recursive_scan_folder(path=TEST_IMAGES)
for file in files:
file_name = file.split(os.sep)[2:][0]
file_name = file_name.split("_")
if file_name[0] == brand and file_name[1] == model:
shutil.copy(src=file, dst=TEST_FOLDER)

10
tests/start_tests.sh Executable file
View File

@@ -0,0 +1,10 @@
python3.12 -m pytest \
--no-header \
-rfp \
--cov \
--cov-report html:tests/coverage \
--cov-report xml:tests/coverage/coverage.xml \
--junitxml=tests/coverage/report.xml \
tests/
exit $?

13
tests/start_tests_gitlab.sh Executable file
View File

@@ -0,0 +1,13 @@
apk add --update libmagic
pip install --upgrade pip
python3 -m pip install -r requirements.txt
python3 -m pip install -r develop_requirements.txt
pwd
sh tests/start_tests.sh
exit_code=$?
cp tests/coverage/coverage.xml ./coverage.xml
cp tests/coverage/report.xml ./report.xml
python3 tests/get_coverage_percent.py
exit $exit_code

24
tests/test_config.py Normal file
View File

@@ -0,0 +1,24 @@
import unittest
from src import config as config_module
class TestConfig(unittest.TestCase):
def test_config_class(self):
test_config = {
"src": "/src/src",
"dst": "/src/dst",
"language": "en"
}
config = config_module.Config(test_config)
assert config.src == "/src/src"
assert config.dst == "/src/dst"
assert config.language == "en"
def test_get_config(self):
config_module.config_file = "src/config.yml"
config = config_module.get_config()
assert config.src == "../app/Temp"
assert config.dst == "../app/Bilder"
assert config.language == "en"

21
tests/test_exif_data.py Normal file
View File

@@ -0,0 +1,21 @@
from src.exif_data import ExifData
import unittest
class TestExifData(unittest.TestCase):
def test_exif_data(self):
exif_data_dict = {
"day": 2,
"month": 2,
"year": 2222,
"make": "CAMERA",
"time": "10:10:10",
"image_path": "/path/to/image",
"image_name": "Image.jpeg"
}
exif_data = ExifData(exif_data_dict)
assert exif_data.make == "CAMERA"
assert exif_data.year == 2222
assert exif_data.time == "10:10:10"

25
tests/test_file_handle.py Normal file
View File

@@ -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) != ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

View File

@@ -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)

24
tests/test_mime_types.py Normal file
View File

@@ -0,0 +1,24 @@
import unittest
from src.mime_types import MimeTypes
class TestMimeTypes(unittest.TestCase):
def test_mime_type_image(self):
mime_type = MimeTypes(file_path="tests/test_files/test_image_001.JPG")
assert mime_type.is_image
assert not mime_type.is_video
assert not mime_type.is_unsupported_file_type
def test_mime_type_video(self):
mime_type = MimeTypes(file_path="tests/test_files/test_video.mp4")
assert not mime_type.is_image
assert mime_type.is_video
assert not mime_type.is_unsupported_file_type
def test_mime_type_unsupported_file_type(self):
mime_type = MimeTypes(file_path="tests/test_mime_types.py")
assert not mime_type.is_image
assert not mime_type.is_video
assert mime_type.is_unsupported_file_type

14
tests/test_process.py Normal file
View File

@@ -0,0 +1,14 @@
import unittest
from unittest.mock import Mock
from src import config
from src.process import start_process
config.config_file = "src/config.yml"
class TestProcess(unittest.TestCase):
def test_process(self):
start_process(logger=Mock())

46
tests/test_scan_dir.py Normal file
View File

@@ -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

View File

@@ -0,0 +1,52 @@
import unittest
from helpers.folder_helper import TEST_FOLDER
from helpers.folder_helper import copy_images
from src.meta_data_handler import get_meta_data
from src.scan_folder import recursive_scan_folder
class TestSamsung(unittest.TestCase):
def test_a54(self):
copy_images(brand="samsung", model="a54")
files = recursive_scan_folder(path=TEST_FOLDER)
meta_data = get_meta_data(images=files)
for image in meta_data:
assert image.make == "samsung"
image = next((image for image in meta_data if image.name == "samsung_a54_001.jpg"), None)
assert image.day == 2
assert image.month == 12
assert image.year == 2023
image = next((image for image in meta_data if image.name == "samsung_a54_003.jpg"), None)
assert image.day == 8
assert image.month == 12
assert image.year == 2023
@unittest.skip("")
def test_a52s(self):
copy_images(brand="samsung", model="a52s")
files = recursive_scan_folder(path=TEST_FOLDER)
meta_data = get_meta_data(images=files)
for image in meta_data:
assert image.make == "samsung"
@unittest.skip("")
def test_a14(self):
copy_images(brand="samsung", model="a14")
files = recursive_scan_folder(path=TEST_FOLDER)
meta_data = get_meta_data(images=files)
for image in meta_data:
assert image.make == "samsung"
class TestApple(unittest.TestCase):
def test_iphone_x(self):
copy_images(brand="iphone", model="x")
files = recursive_scan_folder(path=TEST_FOLDER)
meta_data = get_meta_data(images=files)
for image in meta_data:
assert image.make == "Apple"

View File

@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="0" tests="0" time="0.024" timestamp="2024-03-17T01:13:57.781687" hostname="framework-13" /></testsuites>