diff --git a/.gitignore b/.gitignore index 1786a2f..749a4c4 100644 --- a/.gitignore +++ b/.gitignore @@ -53,5 +53,8 @@ docs/_build/ # PyBuilder target/ +# Various IDEs +/.idea + # python-fitparse specific FitSDK* diff --git a/README.md b/README.md index e4239da..7d79657 100644 --- a/README.md +++ b/README.md @@ -3,17 +3,26 @@ python-fitparse Here's a Python library to parse ANT/Garmin `.FIT` files. -Welcome to python-fitparse! After a few years of laying dormant we are back to -active development! The old version is archived as -[`v1-archive`](https://github.com/dtcooper/python-fitparse/releases/tag/v1-archive). - -The FIT (Flexible and Interoperable Data Transfer) file protocol is specified by -ANT (http://www.thisisant.com/) and an SDK is available for download at -http://www.thisisant.com/pages/products/fit-sdk. +Install from [![PyPI](https://img.shields.io/pypi/v/fitparse.svg)](https://pypi.python.org/pypi/fitparse/): +``` +pip install fitparse +``` +FIT files +------------ +- FIT files contain data stored in a binary file format. +- The FIT (Flexible and Interoperable Data Transfer) file protocol is specified + by [ANT](http://www.thisisant.com/). +- The SDK, code examples, and detailed documentation can be found in the + [ANT FIT SDK](http://www.thisisant.com/resources/fit). + Major Changes From Original Version ----------------------------------- +After a few years of laying dormant we are back to active development! +The old version is archived as +[`v1-archive`](https://github.com/dtcooper/python-fitparse/releases/tag/v1-archive). + * New, hopefully cleaner public API with a clear division between accessible and internal parts. (Still unstable and partially complete.) @@ -53,13 +62,17 @@ Major Changes From Original Version Updating to new FIT SDK versions -------------------------------- -Download the latest SDK from http://www.thisisant.com/pages/products/fit-sdk - -Update the profile: +- Download the latest [ANT FIT SDK](http://www.thisisant.com/resources/fit). +- Update the profile: ``` python3 scripts/generate_profile.py /path/to/fit_sdk.zip fitparse/profile.py ``` +Donations +--------- + +You can donate to Bitcoin address: `1PooPyStXWKZGUNqKYq3zNgTy6iutowaqp` + License ------- diff --git a/fitparse/__init__.py b/fitparse/__init__.py index 49951b3..1bbc49e 100644 --- a/fitparse/__init__.py +++ b/fitparse/__init__.py @@ -1,9 +1,10 @@ from fitparse.base import FitFile, FitParseError +from fitparse.records import DataMessage from fitparse.processors import FitFileDataProcessor, StandardUnitsDataProcessor __version__ = '1.0.1' __all__ = [ 'FitFileDataProcessor', 'FitFile', 'FitParseError', - 'StandardUnitsDataProcessor', + 'StandardUnitsDataProcessor', 'DataMessage' ] diff --git a/scripts/fitdump b/scripts/fitdump index 95a7e76..5068456 100755 --- a/scripts/fitdump +++ b/scripts/fitdump @@ -1,7 +1,12 @@ #!/usr/bin/env python +from __future__ import print_function import argparse +import codecs +import datetime +import json import sys +import types # Python 2 compat try: @@ -13,20 +18,21 @@ except NameError: import fitparse -def format_message(message, options): - s = message.name +def format_message(num, message, options): + s = ["{}. {}".format(num, message.name)] if options.with_defs: - s += ' [%s]' % message.type - s += '\n' + s.append(' [{}]'.format(message.type)) + s.append('\n') if message.type == 'data': for field_data in message: - s += ' * %s: %s' % (field_data.name, field_data.value) + s.append(' * {}: {}'.format(field_data.name, field_data.value)) if field_data.units: - s += ' [%s]' % field_data.units - s += '\n' + s.append(' [{}]'.format(field_data.units)) + s.append('\n') - return s + s.append('\n') + return "".join(s) def parse_args(args=None): @@ -36,11 +42,12 @@ def parse_args(args=None): ) parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument( - '-o', '--output', type=argparse.FileType(mode='wb'), - help='File to output to.', + '-o', '--output', type=argparse.FileType(mode='w'), default="-", + help='File to output data into (defaults to stdout)', ) parser.add_argument( - '-t', '--type', choices=('csv', 'excel', 'readable'), default='readable', + # TODO: csv + '-t', '--type', choices=('readable', 'json'), default='readable', help='File type to output. (DEFAULT: %(default)s)', ) parser.add_argument( @@ -56,20 +63,36 @@ def parse_args(args=None): options = parser.parse_args(args) - if (options.type != 'readable') and not options.output: - parser.error('Please specify an output file (-o) or set --type readable') + # Work around argparse.FileType not accepting an `encoding` kwarg in + # Python < 3.4 by closing and reopening the file (unless it's stdout) + if options.output is not sys.stdout: + options.output.close() + options.output = codecs.open(options.output.name, 'w', encoding='UTF-8') - options.with_defs = (options.verbose >= 1) - options.print_messages = (options.type == 'readable') - options.print_stream = (options.output or sys.stdout) - - if not options.print_messages and (options.verbose >= 1): - options.print_messages = True - options.print_stream = sys.stdout + options.verbose = options.verbose >= 1 + options.with_defs = (options.type == "readable" and options.verbose) + options.as_dict = (options.type != "readable" and options.verbose) return options +class RecordJSONEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, types.GeneratorType): + return list(obj) + if isinstance(obj, datetime.datetime): + return obj.isoformat() + if isinstance(obj, fitparse.DataMessage): + return { + "type": obj.name, + "data": { + data.name: data.value for data in obj + } + } + # Fall back to original to raise a TypeError + return super(RecordJSONEncoder, self).default(obj) + + def main(args=None): options = parse_args(args) @@ -78,14 +101,18 @@ def main(args=None): data_processor=fitparse.StandardUnitsDataProcessor(), check_crc = not(options.ignore_crc), ) - messages = fitfile.get_messages( + records = fitfile.get_messages( name=options.name, with_definitions=options.with_defs, + as_dict=options.as_dict ) - for n, message in enumerate(messages, 1): - if options.print_messages: - print('{}. {}'.format(n, format_message(message, options), file=options.print_stream)) + if options.type == "json": + json.dump(records, fp=options.output, cls=RecordJSONEncoder) + elif options.type == "readable": + options.output.writelines(format_message(n, record, options) + for n, record in enumerate(records, 1)) + if __name__ == '__main__': try: