Skip to content

Commit 578ff1f

Browse files
committed
Fix timestamp fixing
Sort activity points only by distance ignoring timestamps Fix using string together with `Path` Fix not working CLI
1 parent 41b2886 commit 578ff1f

File tree

14 files changed

+76
-43
lines changed

14 files changed

+76
-43
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
Точки маршрута привязываются к тренировке по расстоянию от начала пути.
77

8+
Автоматически исправляет неверные временные метки в файле активности.
9+
810
## Использование
911

1012
[Пример результата](https://www.strava.com/activities/11471378450)
@@ -14,10 +16,11 @@
1416
```shell
1517
> firome --route test/2024-05-21_1597851131.gpx --recording test/40B032FC.fit
1618

17-
route: test/2024-05-21_1597851131.gpx
18-
recording: test/40B032FC.fit.zip
19+
route: /home/akachurin/Downloads/13.06.25.gpx
20+
recording: /home/akachurin/Downloads/42AE8F03.fit.zip
21+
found 16 broken timestamps in activity
1922

20-
result: 1716922421.tcx
23+
result: 1750283535.tcx
2124
```
2225

2326
### GUI
@@ -49,7 +52,6 @@ _Язык приложения зависит от языка системы_
4952
- Должен быть ZIP архивом
5053
- Должен содержать один файл с активностью/маршрутом
5154
- Дополнительно для UI:
52-
- Поддерживаются только для фалов активности
5355
- Должны иметь расширение `.fit.zip`
5456

5557

Taskfile.yaml

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,55 +2,70 @@ version: "3"
22

33
tasks:
44
_prepare:
5+
internal: true
56
cmds:
67
- pip install -r requirements.txt
78

89
run:
10+
desc: Run the CLI application
911
deps:
1012
- _prepare
1113
cmds:
1214
- python -m firome {{.CLI_ARGS}}
1315

1416
generate:
17+
desc: Generate UI files
1518
cmds:
1619
- pyside6-uic firome/ui/assets/main.ui -o firome/ui/main_ui.py
1720

1821
ui:
22+
desc: Run the UI application
1923
deps:
2024
- _prepare
2125
cmds:
2226
- python -m firome.ui
2327

24-
build:
28+
build:cli:
29+
desc: Build the CLI application
2530
deps:
2631
- _prepare
2732
preconditions:
28-
- pip install pyinstaller # TODO: use tox or other dependency splitting
33+
- pip install pyinstaller # TODO: use tox or other dependency splitting
2934
cmds:
3035
- pyinstaller --onefile --console --name firome firome/__main__.py
3136

32-
build-ui:
37+
build:ui:
38+
desc: Build the UI application
3339
deps:
3440
- _prepare
3541
preconditions:
36-
- pip install pyinstaller # TODO: use tox or other dependency splitting
42+
- pip install pyinstaller # TODO: use tox or other dependency splitting
3743
cmds:
3844
- pyinstaller --onefile --windowed --add-data "firome/i18n/assets/*.ini:firome/i18n/assets" --name firome-ui firome/ui/__main__.py
3945

46+
build:all:
47+
desc: Build both the CLI and UI applications
48+
deps:
49+
- build:cli
50+
- build:ui
51+
4052
lint:
53+
desc: Run the linter
4154
preconditions:
42-
- pip install ruff # TODO: use tox or other dependency splitting
55+
- pip install ruff # TODO: use tox or other dependency splitting
4356
cmds:
4457
- ruff check
45-
46-
lint-fix:
58+
59+
lint:fix:
60+
desc: Run the linter and fix issues
4761
preconditions:
48-
- pip install ruff # TODO: use tox or other dependency splitting
62+
- pip install ruff # TODO: use tox or other dependency splitting
4963
cmds:
5064
- ruff check --fix
5165

5266
format:
67+
desc: Run the formatter
5368
preconditions:
54-
- pip install ruff # TODO: use tox or other dependency splitting
69+
- pip install ruff # TODO: use tox or other dependency splitting
5570
cmds:
5671
- ruff format

firome/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "v0.4.4"
1+
__version__ = "v0.4.5"

firome/__main__.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
import logging
33
import sys
44
import time
5+
from pathlib import Path
56

67
from firome import __version__
8+
from firome.codecs.fit import parse_fit
9+
from firome.codecs.gpx import interpolate, parse_gpx
710
from firome.codecs.tcx import export_as_tcx
811
from firome.logger import LOGGER
912
from firome.merge import merge
@@ -16,10 +19,10 @@ def __no_prio_args():
1619
parser = argparse.ArgumentParser(
1720
description="Combines GPX file with training data with GPX file with position data based on the distance",
1821
)
19-
parser.add_argument("--route", type=str, required=__no_prio_args(),
20-
help="Path to GPX file with route of the training")
21-
parser.add_argument("--recording", type=str, required=__no_prio_args(),
22-
help="Path to FIT file with GPS-less data of the training")
22+
parser.add_argument("--route", type=Path, required=__no_prio_args(), help="Path to GPX file with route of the training")
23+
parser.add_argument(
24+
"--recording", type=Path, required=__no_prio_args(), help="Path to FIT file with GPS-less data of the training",
25+
)
2326
parser.add_argument("--precision", type=float, default=1.0, help="Precision of interpolation, meters")
2427
parser.add_argument("--output", choices=["tcx"], default="tcx", help="Output format")
2528
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
@@ -29,7 +32,8 @@ def __no_prio_args():
2932
args = parser.parse_args()
3033

3134
if args.version:
32-
print(__version__) # noqa: T201 # not for debug
35+
print("Firome version", __version__) # noqa: T201 # not for debug
36+
print("Python version", sys.version) # noqa: T201 # not for debug
3337
sys.exit(0)
3438

3539
LOGGER.info("route: %s", args.route)
@@ -38,7 +42,10 @@ def __no_prio_args():
3842
if args.debug:
3943
LOGGER.setLevel(logging.DEBUG)
4044

41-
points = merge(args.route, args.recording, args.precision)
45+
route_points = interpolate(parse_gpx(args.route), args.precision)
46+
activity_points = parse_fit(args.recording.resolve())
47+
48+
points = merge(route_points, activity_points, args.precision)
4249

4350
if args.output == "tcx":
4451
output_path = f"{int(time.time())}.tcx"

firome/codecs/fit/parse.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import datetime, timezone
2+
from pathlib import Path
23

34
from fitdecode import FIT_FRAME_DATA, FitDataMessage, FitReader
45

@@ -10,20 +11,22 @@
1011
__max_delta_days = 120 # expected activity date range from now
1112

1213

13-
def parse_fit(src: str) -> list[DataPoint]:
14+
def parse_fit(src: Path) -> list[DataPoint]:
1415
"""Parse FIT file by given path."""
15-
if src.lower().endswith(".zip"):
16+
if src.suffix.lower() == ".zip":
1617
src = unzip(src)
1718

18-
if not src.lower().endswith(".fit"):
19+
if not src.suffix.lower() == ".fit":
1920
raise UnsupportedFileExtError(src)
2021

2122
with FitReader(src) as fit:
2223
return FitParser(fit).process()
2324

25+
2426
class FitParserRecreatedError(Exception):
2527
"""__FitParser был создан второй раз."""
2628

29+
2730
class FitParser:
2831
"""FIT file parser."""
2932

@@ -53,7 +56,6 @@ def process(self):
5356

5457
self._closed = True
5558

56-
result.sort(key=lambda x: x.timestamp)
5759
result.sort(key=lambda x: x.distance)
5860

5961
# expecting sorted list here
@@ -104,7 +106,7 @@ def _ts_ok(prev, curr, nxt):
104106
return False
105107

106108
# if next < prev, then next is broken
107-
if (curr > nxt) and (nxt >= prev):
109+
if curr > nxt >= prev:
108110
return False
109111

110112
return (curr - prev).days == 0

firome/codecs/gpx/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
from .interpolate import interpolate
44
from .parse import parse_gpx
55

6-
__all__ = ["parse_gpx", "interpolate"]
6+
__all__ = ["interpolate", "parse_gpx"]

firome/codecs/gpx/interpolate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def __gpx_calculate_distance(gpx_data: _GPXData, *, use_ele: bool = True) -> lis
102102
if gpx_data["ele"] and use_ele:
103103
dist_ele = gpx_data["ele"][i + 1] - gpx_data["ele"][i]
104104

105-
gpx_dist[i + 1] = np.sqrt(dist_latlon ** 2 + dist_ele ** 2)
105+
gpx_dist[i + 1] = np.sqrt(dist_latlon**2 + dist_ele**2)
106106
else:
107107
gpx_dist[i + 1] = dist_latlon
108108

firome/codecs/gpx/parse.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from pathlib import Path
2+
13
from geopy.distance import geodesic
24
from lxml import etree
35

@@ -7,17 +9,17 @@
79
from ..zip import unzip
810

911

10-
def parse_gpx(src: str) -> list[PositionPoint]:
12+
def parse_gpx(src: Path) -> list[PositionPoint]:
1113
"""Parse GPX file by given path."""
12-
if src.lower().endswith(".zip"):
14+
if src.suffix.lower() == ".zip":
1315
src = unzip(src)
1416

15-
if not src.lower().endswith(".gpx"):
17+
if not src.suffix.lower() == ".gpx":
1618
raise UnsupportedFileExtError(src)
1719

1820
result = []
1921

20-
root: etree.ElementBase = etree.parse(src).getroot() # noqa:S320 # локальное приложение
22+
root: etree.ElementBase = etree.parse(src).getroot() # локальное приложение
2123

2224
default_ns = root.nsmap[None]
2325

firome/codecs/tcx/export.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ def export_as_tcx(points: list[DataPoint], destination: str, fields=None):
2121

2222
root_attrs = {
2323
etree.QName(
24-
"http://www.w3.org/2001/XMLSchema-instance", "schemaLocation",
24+
"http://www.w3.org/2001/XMLSchema-instance",
25+
"schemaLocation",
2526
): "http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2 "
26-
"http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd",
27+
"http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd",
2728
}
2829
root: ElementBase = etree.Element(_with_ns("TrainingCenterDatabase"), root_attrs, nsmap=_namespaces)
2930

firome/codecs/zip/unzip.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .errors import EmptyArchiveError, MultipleFilesError
66

77

8-
def unzip(src: str) -> str:
8+
def unzip(src: Path) -> Path:
99
"""Unzip source archive returning path of extracted file."""
1010
tmp_dir = tempfile.mkdtemp(prefix="firome-")
1111

0 commit comments

Comments
 (0)