ftlr 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ftlr/__init__.py +0 -0
- ftlr/config.py +48 -0
- ftlr/config_bak.py +71 -0
- ftlr/modification.py +21 -0
- ftlr/runner.py +39 -0
- ftlr/xmp.py +61 -0
- ftlr/xmp_types.py +66 -0
- ftlr-0.0.1.dist-info/METADATA +26 -0
- ftlr-0.0.1.dist-info/RECORD +12 -0
- ftlr-0.0.1.dist-info/WHEEL +5 -0
- ftlr-0.0.1.dist-info/entry_points.txt +2 -0
- ftlr-0.0.1.dist-info/top_level.txt +1 -0
ftlr/__init__.py
ADDED
|
File without changes
|
ftlr/config.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cache
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from ftlr.modification import Modification
|
|
6
|
+
from ftlr.xmp_types import XmpType, Factory
|
|
7
|
+
|
|
8
|
+
__types = Factory.instance()
|
|
9
|
+
|
|
10
|
+
__descriptions = [
|
|
11
|
+
("crs:Exposure2012", "exposure", __types.real(), -5, 5),
|
|
12
|
+
("crs:Contrast2012", "contrast", __types.integer(), -100, 100)
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class Config:
|
|
18
|
+
key: str
|
|
19
|
+
name: str
|
|
20
|
+
type: XmpType
|
|
21
|
+
min: float
|
|
22
|
+
max: float
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
@cache
|
|
26
|
+
def args(self) -> List[str]:
|
|
27
|
+
short_name = f"-{self.name[0]}"
|
|
28
|
+
long_name = f"--{self.name}"
|
|
29
|
+
|
|
30
|
+
return [
|
|
31
|
+
short_name,
|
|
32
|
+
long_name,
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@cache
|
|
37
|
+
def kwargs(self):
|
|
38
|
+
return {
|
|
39
|
+
"type": self.build_modification
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def build_modification(self, value: str) -> Modification:
|
|
43
|
+
assert value.startswith("+") or value.startswith("-")
|
|
44
|
+
|
|
45
|
+
return Modification(self.key, self.type, self.type.python_type(value))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
CONFIG = [Config(*desc) for desc in __descriptions]
|
ftlr/config_bak.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cache
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Iterable, List
|
|
5
|
+
|
|
6
|
+
from ftlr.modifier import Modification
|
|
7
|
+
from ftlr.xmp_types import XmpType, Factory
|
|
8
|
+
|
|
9
|
+
__types = Factory.instance()
|
|
10
|
+
|
|
11
|
+
__dict_mapping = {
|
|
12
|
+
"x:xmpmeta": {
|
|
13
|
+
"rdf:RDF": {
|
|
14
|
+
"rdf:Description": {
|
|
15
|
+
"crs:Exposure2012": ("exposure", __types.real()),
|
|
16
|
+
"crs:Contrast2012": ("contrast", __types.integer())
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class Config:
|
|
25
|
+
path: Path
|
|
26
|
+
name: str
|
|
27
|
+
type: XmpType
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@cache
|
|
31
|
+
def args(self) -> List[str]:
|
|
32
|
+
short_name = f"-{self.name[0]}"
|
|
33
|
+
long_name = f"--{self.name}"
|
|
34
|
+
|
|
35
|
+
return [
|
|
36
|
+
short_name,
|
|
37
|
+
long_name,
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
@cache
|
|
42
|
+
def kwargs(self):
|
|
43
|
+
return {
|
|
44
|
+
"type": self.build_modification
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
def build_modification(self, value: str) -> Modification:
|
|
48
|
+
assert value.startswith("+") or value.startswith("-")
|
|
49
|
+
|
|
50
|
+
return Modification(self.path, self.type, self.type.python_type(value))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def __flatten_dict(d: Dict, path: Path) -> Iterable[Config]:
|
|
54
|
+
result = []
|
|
55
|
+
|
|
56
|
+
for key, value in d.items():
|
|
57
|
+
key_path = path / key
|
|
58
|
+
|
|
59
|
+
if isinstance(value, tuple):
|
|
60
|
+
name, type_ = value
|
|
61
|
+
|
|
62
|
+
result.append(Config(key_path, name, type_))
|
|
63
|
+
elif isinstance(value, dict):
|
|
64
|
+
result += __flatten_dict(value, key_path)
|
|
65
|
+
else:
|
|
66
|
+
assert False
|
|
67
|
+
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
CONFIG = __flatten_dict(__dict_mapping, Path())
|
ftlr/modification.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from ftlr.xmp_types import XmpType, ValueType
|
|
4
|
+
from ftlr.xmp import Xmp
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Modification:
|
|
9
|
+
key: str
|
|
10
|
+
xmp_type: XmpType
|
|
11
|
+
value: ValueType
|
|
12
|
+
|
|
13
|
+
def apply(self, xmp: Xmp):
|
|
14
|
+
original_str = xmp[self.key]
|
|
15
|
+
original_value = self.xmp_type.from_string(original_str)
|
|
16
|
+
|
|
17
|
+
modified_value = original_value + self.value
|
|
18
|
+
modified_str = self.xmp_type.to_string(modified_value)
|
|
19
|
+
|
|
20
|
+
xmp[self.key] = modified_str
|
|
21
|
+
|
ftlr/runner.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
from glob import iglob
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ftlr import config
|
|
6
|
+
from ftlr.modification import Modification
|
|
7
|
+
from ftlr.xmp import Xmp
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run():
|
|
11
|
+
print("running")
|
|
12
|
+
|
|
13
|
+
parser = ArgumentParser()
|
|
14
|
+
|
|
15
|
+
for cfg in config.CONFIG:
|
|
16
|
+
parser.add_argument(*cfg.args, **cfg.kwargs)
|
|
17
|
+
|
|
18
|
+
# parser.add_argument("pattern", type=Path, nargs="?", default=Path.cwd() / "*")
|
|
19
|
+
parser.add_argument("pattern", nargs="?", default="*")
|
|
20
|
+
|
|
21
|
+
namespace = parser.parse_args("-e -1.5 ../../25.*/*.xmp".split())
|
|
22
|
+
|
|
23
|
+
modifications = [mod for mod in vars(namespace).values() if isinstance(mod, Modification)]
|
|
24
|
+
|
|
25
|
+
for str_path in iglob(namespace.pattern):
|
|
26
|
+
path = Path(str_path)
|
|
27
|
+
|
|
28
|
+
assert path.suffix == Xmp.SUFFIX
|
|
29
|
+
|
|
30
|
+
with Xmp.read(path) as xmp:
|
|
31
|
+
for modification in modifications:
|
|
32
|
+
modification.apply(xmp)
|
|
33
|
+
|
|
34
|
+
# if error:
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == '__main__':
|
|
39
|
+
run()
|
ftlr/xmp.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, Generator, Self, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# todo: add context management
|
|
7
|
+
class Xmp:
|
|
8
|
+
SEPARATOR = "/"
|
|
9
|
+
SUFFIX = ".xmp"
|
|
10
|
+
|
|
11
|
+
def __init__(self, lines: List[str]) -> None:
|
|
12
|
+
super().__init__()
|
|
13
|
+
|
|
14
|
+
self.__lines = lines
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def __value(s: str) -> str:
|
|
18
|
+
s = s.strip()
|
|
19
|
+
|
|
20
|
+
key, value = s.split("=")
|
|
21
|
+
|
|
22
|
+
value = value.strip("\"")
|
|
23
|
+
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
def __getitem__(self, key: str) -> str:
|
|
27
|
+
good_lines = [line for line in self.__lines if key in line]
|
|
28
|
+
|
|
29
|
+
assert len(good_lines) == 1
|
|
30
|
+
|
|
31
|
+
line = good_lines[0]
|
|
32
|
+
|
|
33
|
+
value = Xmp.__value(line)
|
|
34
|
+
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
def __setitem__(self, key: str, new_value: str):
|
|
38
|
+
good_lines = [(index, line) for index, line in enumerate(self.__lines) if key in line]
|
|
39
|
+
|
|
40
|
+
assert len(good_lines) == 1
|
|
41
|
+
|
|
42
|
+
index, line = good_lines[0]
|
|
43
|
+
|
|
44
|
+
old_value = Xmp.__value(line)
|
|
45
|
+
|
|
46
|
+
line = line.replace(old_value, new_value)
|
|
47
|
+
|
|
48
|
+
self.__lines[index] = line
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
@contextmanager
|
|
52
|
+
def read(cls, path: Path) -> Generator[Self, Any, None]:
|
|
53
|
+
with path.open() as xmp_file:
|
|
54
|
+
lines = xmp_file.readlines()
|
|
55
|
+
|
|
56
|
+
xmp = cls(lines)
|
|
57
|
+
|
|
58
|
+
yield xmp
|
|
59
|
+
|
|
60
|
+
with path.open("w") as xmp_file:
|
|
61
|
+
xmp_file.writelines(xmp.__lines)
|
ftlr/xmp_types.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from functools import cache
|
|
3
|
+
from typing import Type
|
|
4
|
+
|
|
5
|
+
ValueType = float | int
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class XmpType:
|
|
9
|
+
@property
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def python_type(self) -> Type[ValueType]:
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def from_string(self, value: str) -> ValueType:
|
|
15
|
+
return self.python_type(value)
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def prepare_value(self, value: ValueType) -> ValueType:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def to_string(self, value: ValueType) -> str:
|
|
22
|
+
if value > 0:
|
|
23
|
+
sign = "+"
|
|
24
|
+
elif value < 0:
|
|
25
|
+
sign = "-"
|
|
26
|
+
else:
|
|
27
|
+
sign = ""
|
|
28
|
+
|
|
29
|
+
value = abs(self.prepare_value(value))
|
|
30
|
+
|
|
31
|
+
return sign + str(value)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class XmpReal(XmpType):
|
|
35
|
+
@property
|
|
36
|
+
def python_type(self) -> Type[ValueType]:
|
|
37
|
+
return float
|
|
38
|
+
|
|
39
|
+
def prepare_value(self, value: ValueType) -> ValueType:
|
|
40
|
+
return round(value, ndigits=2)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class XmpInteger(XmpType):
|
|
44
|
+
@property
|
|
45
|
+
def python_type(self) -> Type[ValueType]:
|
|
46
|
+
return int
|
|
47
|
+
|
|
48
|
+
def prepare_value(self, value: ValueType) -> ValueType:
|
|
49
|
+
return int(round(value))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Factory:
|
|
53
|
+
__instance = None
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
@cache
|
|
57
|
+
def instance(cls) -> "Factory":
|
|
58
|
+
return Factory()
|
|
59
|
+
|
|
60
|
+
@cache
|
|
61
|
+
def real(self) -> XmpType:
|
|
62
|
+
return XmpReal()
|
|
63
|
+
|
|
64
|
+
@cache
|
|
65
|
+
def integer(self) -> XmpType:
|
|
66
|
+
return XmpInteger()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ftlr
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Home-page:
|
|
5
|
+
Author: Igor Djachenko
|
|
6
|
+
Author-email: i.s.djachenko@gmail.com
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/djachenko/ftlr
|
|
9
|
+
Project-URL: Repository, https://github.com/djachenko/ftlr
|
|
10
|
+
Project-URL: Issues, https://github.com/djachenko/ftlr/issues
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: xmltodict
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest; extra == "test"
|
|
16
|
+
Requires-Dist: ruff; extra == "test"
|
|
17
|
+
Requires-Dist: mypy; extra == "test"
|
|
18
|
+
Requires-Dist: types-xmltodict; extra == "test"
|
|
19
|
+
Provides-Extra: release
|
|
20
|
+
Requires-Dist: build; extra == "release"
|
|
21
|
+
Requires-Dist: python-semantic-release; extra == "release"
|
|
22
|
+
Dynamic: author-email
|
|
23
|
+
|
|
24
|
+
# ftlr
|
|
25
|
+
|
|
26
|
+
Usage: `~$ ftlr [--exposure EXPOSURE]`
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ftlr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
ftlr/config.py,sha256=vSBFG4pq5Ds72bTBQgzcN9yKcynmZhQhmh19sDmm8os,1058
|
|
3
|
+
ftlr/config_bak.py,sha256=84F2PxSi5LppXepkdo1rTnqecjjLp511GAaTp7TUm8c,1597
|
|
4
|
+
ftlr/modification.py,sha256=ai2vkJcmBVBlieC-yEsKuw3ifgH1D_qk6YHH7zWQHMM,498
|
|
5
|
+
ftlr/runner.py,sha256=rqPYgtupNG7b1vrZ_LcjXqsv9b-t0fuFiAK_wtyys5M,950
|
|
6
|
+
ftlr/xmp.py,sha256=AM64z-opcxLLPa0f_n5uVxIXmOy86efDGdVpuZH1mfI,1362
|
|
7
|
+
ftlr/xmp_types.py,sha256=VlWQLI99T-BBWtywZ0bxNTEkyAa_t-h3OJTrcZtnbkc,1345
|
|
8
|
+
ftlr-0.0.1.dist-info/METADATA,sha256=ZHnz1fY8LuCUbMZaQy6gmui_uaVRmHEhTJW_K0Va8W0,788
|
|
9
|
+
ftlr-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
ftlr-0.0.1.dist-info/entry_points.txt,sha256=UknK7g-IzxMg-VvoK4JwikVw0ox2bQRvPvv_feoe3iE,41
|
|
11
|
+
ftlr-0.0.1.dist-info/top_level.txt,sha256=wVpZu7b0tE3PxJRoBbOodJxSD9IOqaIOhy74ID5G280,5
|
|
12
|
+
ftlr-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ftlr
|