Skip to content

Commit

Permalink
Merge branch 'typing'
Browse files Browse the repository at this point in the history
  • Loading branch information
153957 committed Jun 16, 2023
2 parents 5a2041b + 2cb24ba commit 44a9788
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 141 deletions.
32 changes: 29 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,40 @@ on:
push:

jobs:
flake8:
rufftest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: 'setup.cfg'
cache-dependency-path: 'requirements-ruff.txt'
- run: make ruffinstall
- run: make rufftest
env:
RUFF_FORMAT: github

mypytest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- run: make devinstall
- run: make mypytest

unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: 'pyproject.toml'
- run: make devinstall
- run: make flaketest
- run: make unittest
21 changes: 15 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
.PHONY: ruffinstall
ruffinstall:
pip install --upgrade pip
pip install --upgrade --upgrade-strategy eager -r requirements-ruff.txt

.PHONY: devinstall
devinstall:
pip install --upgrade pip
pip install --upgrade --upgrade-strategy eager --editable .[dev]

.PHONY: test
test: flaketest checksetup unittests
test: rufftest mypytest checksetup unittests

.PHONY: rufftest
rufftest:
ruff .

.PHONY: flaketest
flaketest:
flake8
.PHONY: mypytest
mypytest:
mypy .

.PHONY: checksetup
checksetup:
python setup.py check -ms

.PHONY: unittests
unittests:
.PHONY: unittest
unittest:
python -m unittest discover --catch --start-directory tests --top-level-directory .

.PHONY: clean
Expand Down
113 changes: 113 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
[build-system]
requires = ['flit_core>=3.9']
build-backend = 'flit_core.buildapi'

[project]
name = 'time-lapse'
version = '3'
description = 'Time-lapse movie assembly'
readme = 'README.md'
requires-python = '>=3.11'
license = {file = 'LICENSE'}
authors = [
{name = 'Arne de Laat', email = 'arne@delaat.net'},
]
maintainers = [
{name = 'Arne de Laat', email = 'arne@delaat.net'},
]
keywords = [
'ffmpeg',
'photography',
'time-lapse',
]
classifiers = [
'Intended Audience :: Developers',
'Intended Audience :: Other Audience',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Topic :: Multimedia :: Video :: Conversion',
]
dependencies = [
'exifreader',
'ffmpeg-python',
]

[project.optional-dependencies]
graph = [
'graphviz',
]
dev = [
'coverage',
'flit',
'mypy',
]

[project.urls]
Homepage = 'https://arne.delaat.net/timelapse.html'
Repository = 'https://github.com/153957/time-lapse'

[project.scripts]
timelapse = 'time_lapse.cli:main'
check_interval = 'time_lapse.check_interval:main'

[tool.mypy]
ignore_missing_imports = true
show_column_numbers = true
show_error_codes = true
strict = true
warn_return_any = true
warn_unused_configs = true

[tool.black]
target-version = ['py311']
line-length = 120
skip-string-normalization = true

[tool.ruff]
target-version = 'py311'
line-length = 120
select = [
# https://github.com/charliermarsh/ruff#supported-rules
'ALL',
'E111', 'E112', 'E113', 'E114', 'E115', 'E116', 'E117',
'E201', 'E202', 'E203',
'E211',
'E221', 'E222', 'E223', 'E224', 'E225', 'E226', 'E227', 'E228',
'E231',
'E251', 'E252',
'E261', 'E262', 'E265', 'E266',
'E271', 'E272', 'E273', 'E274', 'E275',
]
ignore = [
'D', # Ignore docstring checks
'FBT001', # Allow positional for boolean arguments
'FBT002', # Allow default value for boolean arguments
'PD', # Not using pandas
'PLR0913', # Allow functions with many arguments
'Q000', # Use single quotes
'RET504', # Allow variable assignment before return
'SIM108', # Allow if-else block instead of ternary
'T201', # Allow using print
]

[tool.ruff.isort]
lines-between-types = 1
section-order = [
'future',
'standard-library',
'third-party',
'ffmpeg',
'first-party',
'local-folder',
]

[tool.ruff.isort.sections]
ffmpeg = ['ffmpeg']

[tool.coverage.run]
branch = true
source = [
'time_lapse',
'tests',
]
1 change: 1 addition & 0 deletions requirements-ruff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruff==0.0.272
71 changes: 0 additions & 71 deletions setup.cfg

This file was deleted.

3 changes: 0 additions & 3 deletions setup.py

This file was deleted.

50 changes: 27 additions & 23 deletions time_lapse/check_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import datetime
import pathlib

from dataclasses import dataclass
from glob import glob
from operator import itemgetter
from operator import attrgetter

import exifreader

Expand All @@ -12,18 +13,24 @@
MIN_IMAGES_SEQUENCE = 20


def get_image_date(image_path):
def get_image_date(image_path: str) -> datetime.datetime:
"""Get EXIF image date from an image as a datetime"""
with open(image_path, 'rb') as _file:
with pathlib.Path(image_path).open('rb') as _file:
tags = exifreader.process_file(_file, details=False)
date_time = tags['EXIF DateTimeOriginal'].values
subsec = tags['EXIF SubSecTimeOriginal'].values
full_date_time = f'{date_time}.{subsec}'
image_date = datetime.datetime.strptime(full_date_time, '%Y:%m:%d %H:%M:%S.%f')
full_date_time = f'{date_time}.{subsec}+0000'
image_date = datetime.datetime.strptime(full_date_time, '%Y:%m:%d %H:%M:%S.%f%z')
return image_date


def find_sequences(pattern, shots_per_interval, group):
@dataclass
class ImageInfo:
path: pathlib.Path
date: datetime.datetime


def find_sequences(pattern: str, shots_per_interval: int, group: bool) -> None:
skip = shots_per_interval
files = sorted(glob(pattern))

Expand All @@ -32,14 +39,11 @@ def find_sequences(pattern, shots_per_interval, group):
return

image_dates = sorted(
(
{'path': pathlib.Path(path), 'date': get_image_date(path)}
for path in files[::skip]
),
key=itemgetter('date')
(ImageInfo(path=pathlib.Path(path), date=get_image_date(path)) for path in files[::skip]),
key=attrgetter('date'),
)

start_of_sequence = image_dates[0]['path']
start_of_sequence = image_dates[0].path
sequence = [start_of_sequence]
nth_sequence = 1

Expand All @@ -48,14 +52,14 @@ def find_sequences(pattern, shots_per_interval, group):
' n',
'interval',
'sequence',
sep='\t'
sep='\t',
)

for previous, current, following in zip(image_dates[:-2], image_dates[1:-1], image_dates[2:]):
sequence.append(current['path'])
for previous, current, following in zip(image_dates[:-2], image_dates[1:-1], image_dates[2:], strict=True):
sequence.append(current.path)

interval = current['date'] - previous['date']
new_interval = following['date'] - current['date']
interval = current.date - previous.date
new_interval = following.date - current.date

if interval < MIN_INTERVAL or abs(interval - new_interval) > MARGIN:
if len(sequence) > MIN_IMAGES_SEQUENCE:
Expand All @@ -64,34 +68,34 @@ def find_sequences(pattern, shots_per_interval, group):
f'{len(sequence):4}',
f'{interval.total_seconds():7}s',
f'{sequence[0]}{sequence[-1]}',
sep='\t'
sep='\t',
)
if group:
group_sequence(sequence, nth_sequence)
nth_sequence += 1
sequence = [current['path']]
sequence = [current.path]

sequence.append(following['path'])
sequence.append(following.path)
if len(sequence) > MIN_IMAGES_SEQUENCE:
print(
f'{len(sequence):4}',
f'{new_interval.total_seconds():7}s',
f'{sequence[0]}{sequence[-1]}',
sep='\t'
sep='\t',
)
if group:
group_sequence(sequence, nth_sequence)


def group_sequence(sequence, sequence_number):
def group_sequence(sequence: list[pathlib.Path], sequence_number: int) -> None:
"""Group all files in the sequence into a subdirectory in the working directory"""

pathlib.Path(f'sequence_{sequence_number}').mkdir()
for path in sequence:
path.rename(f'sequence_{sequence_number}/{path.name}')


def main():
def main() -> None:
parser = argparse.ArgumentParser(description='.')
parser.add_argument(
'--pattern',
Expand Down

0 comments on commit 44a9788

Please sign in to comment.