Skip to content

Commit 238e87b

Browse files
committed
Initial commit
0 parents  commit 238e87b

8 files changed

Lines changed: 473 additions & 0 deletions

File tree

.clang-format

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Generated from CLion C/C++ Code Style settings
2+
BasedOnStyle: LLVM
3+
AccessModifierOffset: -4
4+
AlignAfterOpenBracket: Align
5+
AlignConsecutiveAssignments: AcrossComments
6+
AlignConsecutiveDeclarations: AcrossComments
7+
AlignOperands: Align
8+
AllowAllArgumentsOnNextLine: false
9+
AllowAllConstructorInitializersOnNextLine: false
10+
AllowAllParametersOfDeclarationOnNextLine: false
11+
AllowShortBlocksOnASingleLine: Always
12+
AllowShortCaseLabelsOnASingleLine: false
13+
AllowShortFunctionsOnASingleLine: All
14+
AllowShortIfStatementsOnASingleLine: Never
15+
AllowShortLambdasOnASingleLine: All
16+
AllowShortLoopsOnASingleLine: true
17+
AlwaysBreakAfterReturnType: None
18+
AlwaysBreakTemplateDeclarations: Yes
19+
BreakBeforeBraces: Custom
20+
BraceWrapping:
21+
AfterCaseLabel: false
22+
AfterClass: false
23+
AfterControlStatement: Never
24+
AfterEnum: false
25+
AfterFunction: false
26+
AfterNamespace: false
27+
AfterUnion: false
28+
BeforeCatch: false
29+
BeforeElse: false
30+
IndentBraces: false
31+
SplitEmptyFunction: false
32+
SplitEmptyRecord: true
33+
BreakBeforeBinaryOperators: None
34+
BreakBeforeTernaryOperators: true
35+
BreakConstructorInitializers: BeforeColon
36+
BreakInheritanceList: BeforeColon
37+
ColumnLimit: 0
38+
CompactNamespaces: false
39+
ContinuationIndentWidth: 8
40+
IndentCaseLabels: true
41+
IndentPPDirectives: None
42+
IndentWidth: 4
43+
KeepEmptyLinesAtTheStartOfBlocks: true
44+
MaxEmptyLinesToKeep: 2
45+
NamespaceIndentation: All
46+
ObjCSpaceAfterProperty: false
47+
ObjCSpaceBeforeProtocolList: true
48+
PointerAlignment: Right
49+
ReflowComments: false
50+
SpaceAfterCStyleCast: true
51+
SpaceAfterLogicalNot: false
52+
SpaceAfterTemplateKeyword: false
53+
SpaceBeforeAssignmentOperators: true
54+
SpaceBeforeCpp11BracedList: false
55+
SpaceBeforeCtorInitializerColon: true
56+
SpaceBeforeInheritanceColon: true
57+
SpaceBeforeParens: ControlStatements
58+
SpaceBeforeRangeBasedForLoopColon: false
59+
SpaceInEmptyParentheses: false
60+
SpacesBeforeTrailingComments: 0
61+
SpacesInAngles: false
62+
SpacesInCStyleCastParentheses: false
63+
SpacesInContainerLiterals: false
64+
SpacesInParentheses: false
65+
SpacesInSquareBrackets: false
66+
TabWidth: 4
67+
UseTab: Never

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# CLion
2+
.idea/
3+
4+
# CMake & Make
5+
CMakeCache.txt
6+
CMakeFiles/
7+
Makefile
8+
cmake-build-debug/
9+
cmake_install.cmake
10+
11+
# Output
12+
build.h
13+
libtinyfseq.a

CMakeLists.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
cmake_minimum_required(VERSION 3.21)
2+
3+
project(tinyfseq
4+
VERSION 1.0.0
5+
DESCRIPTION "A tiny library for decoding FSEQ (.fseq) v2.0+ sequence files"
6+
HOMEPAGE_URL "https://github.com/Cryptkeeper/libtinyfseq"
7+
LANGUAGES C)
8+
9+
set(CMAKE_C_STANDARD 90)
10+
11+
set(TF_INCLUDE_ERR_STRINGS true)
12+
13+
configure_file(${PROJECT_SOURCE_DIR}/build.h.in ${PROJECT_SOURCE_DIR}/build.h)
14+
15+
add_library(tinyfseq tinyfseq.h tinyfseq.c)
16+
17+
set_target_properties(tinyfseq PROPERTIES PUBLIC_HEADER "tinyfseq.h")
18+
19+
install(TARGETS tinyfseq
20+
LIBRARY DESTINATION lib/tinyfseq
21+
PUBLIC_HEADER DESTINATION include/tinyfseq)

LICENSE.txt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Nick Krecklow
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# libtinyfseq
2+
3+
A tiny library (~125 LOC) for decoding FSEQ (.fseq) v2.0+ sequence files developed and popularized by
4+
the [fpp](https://github.com/FalconChristmas/fpp) and [xLights](https://github.com/smeighan/xLights) programs.
5+
Additional documentation for the file format is available
6+
at [Cryptkeeper/fseq-file-format](https://github.com/Cryptkeeper/fseq-file-format).
7+
8+
## Installation
9+
10+
libtinyfseq uses [CMake](https://cmake.org/) for building and packaging the library.
11+
12+
- Generate Makefiles using `cmake .`
13+
- Compile the library using `make`
14+
- Optionally install the headers and compiled archive using `make install`
15+
- Include in your project using `#include <tinyfseq/tinyfseq.h>`
16+
17+
If optionally installed, `install_manifest.txt` will be created, containing the installed file paths for easy removal.
18+
19+
## Build Configuration
20+
21+
For devices with limited memory, a `TF_INCLUDE_ERR_STRINGS` definition is included in [CMakeLists.txt](CMakeLists.txt)
22+
to disable the inclusion of error strings in the build. Calls to `tf_err_str` will instead return `"NULL"` (as a string)
23+
.
24+
25+
## Compatibility
26+
27+
- libtinyfseq only supports FSEQ versions v2.x versions, with the schema initially released in 2018. Older v1.x files
28+
can be upgraded using the [xLights](https://github.com/smeighan/xLights) program.
29+
- To minimize dependencies, libtinyfseq does not support compressed FSEQ files as may use either
30+
[zstd](https://github.com/facebook/zstd) or [zlib](https://www.zlib.net)
31+
compression. Compressed FSEQ files can be pre-decompressed using the [xLights](https://github.com/smeighan/xLights)
32+
program, or you may decompress the data buffer yourself using an additional library before passing calling into
33+
libtinyfseq.
34+
- libtinyfseq assumes data buffers are in little endian byte order.
35+
36+
## Usage
37+
38+
libtinyfseq only defines three functions for reading the various components of a FSEQ file. See [tinyfseq.h](tinyfseq.h)
39+
for comments describing their usage.
40+
41+
| Function | Schema |
42+
| --- | --- |
43+
| `tf_read_file_header` | https://github.com/Cryptkeeper/fseq-file-format#header |
44+
| `tf_read_var_header` | https://github.com/Cryptkeeper/fseq-file-format#variable |
45+
| `tf_read_channel_range` | https://github.com/Cryptkeeper/fseq-file-format#sparse-range |
46+
47+
Two additional utility functions are provided:
48+
49+
1. `tf_sequence_duration_seconds` for calculating the duration of a given sequence in seconds
50+
2. `tf_err_str` for mutating `enum tf_err_t` values into their string names
51+
52+
## License
53+
54+
See [LICENSE.txt](LICENSE.txt)

build.h.in

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2022 Nick Krecklow
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
#ifndef LIBTINYFSEQ_BUILD_HIN
25+
#define LIBTINYFSEQ_BUILD_HIN
26+
27+
#define TF_VERSION "${PROJECT_VERSION}"
28+
29+
#cmakedefine TF_INCLUDE_ERR_STRINGS
30+
31+
#endif//LIBTINYFSEQ_BUILD_HIN

tinyfseq.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2022 Nick Krecklow
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
#include "tinyfseq.h"
25+
26+
#include <string.h>
27+
28+
const char *tf_err_str(enum tf_err_t err) {
29+
#ifdef TF_INCLUDE_ERR_STRINGS
30+
switch (err) {
31+
case TF_OK:
32+
return "TF_OK (ok)";
33+
case TF_EINVALID_HEADER_SIZE:
34+
return "TF_EINVALID_HEADER_SIZE (undersized `tf_file_header_t` data decoding buffer)";
35+
case TF_EINVALID_MAGIC:
36+
return "TF_EINVALID_MAGIC (invalid magic file signature)";
37+
case TF_EINVALID_COMPRESSION_TYPE:
38+
return "TF_EINVALID_COMPRESSION_TYPE (compressed files are not supported)";
39+
case TF_EINVALID_VAR_HEADER_SIZE:
40+
return "TF_EINVALID_VAR_HEADER_SIZE (undersized `tf_var_header_t` data decoding buffer)";
41+
case TF_EINVALID_VAR_VALUE_SIZE:
42+
return "TF_EINVALID_VAR_VALUE_SIZE (undersized variable value data decoding buffer)";
43+
case TF_EINVALID_CHANNEL_RANGE_SIZE:
44+
return "TF_EINVALID_CHANNEL_RANGE_SIZE (undersized `tf_channel_range_t` data decoding buffer)";
45+
default:
46+
return "unknown `tf_err_t` value";
47+
}
48+
#else
49+
return "NULL";
50+
#endif
51+
}
52+
53+
enum tf_err_t tf_read_file_header(const uint8_t *bd, int bs, struct tf_file_header_t *header, uint8_t **ep) {
54+
// header structure is a fixed 32 byte size according to schema
55+
// https://github.com/Cryptkeeper/fseq-file-format#header
56+
const int FILE_HEADER_SIZE = 32;
57+
58+
if (bs < FILE_HEADER_SIZE) {
59+
return TF_EINVALID_HEADER_SIZE;
60+
}
61+
62+
if (bd[0] != 'P' || bd[1] != 'S' || bd[2] != 'E' || bd[3] != 'Q') {
63+
return TF_EINVALID_MAGIC;
64+
}
65+
66+
// WARNING: this is vulnerable to breaking depending on how `tf_file_header_t` is packed/aligned
67+
// 15 bytes copies from the `.channelDataOffset` field to `.frameStepTimeMillis`
68+
// two additional calls each copy an individual field
69+
memcpy((unsigned char *) &header->channelDataOffset, &bd[4], 15);
70+
71+
// upper 4 bits contain additional compression block count data that is ignored by tinyfseq
72+
// mask to lower 4 bits to filter only the compression type field
73+
if ((bd[20] & 0xF) != 0) {
74+
return TF_EINVALID_COMPRESSION_TYPE;
75+
}
76+
77+
memcpy((unsigned char *) &header->channelRangeCount, &bd[22], 1);
78+
memcpy((unsigned char *) &header->sequenceUid, &bd[24], 8);
79+
80+
if (ep != NULL) {
81+
*ep = ((uint8_t *) bd) + FILE_HEADER_SIZE;
82+
}
83+
84+
return TF_OK;
85+
}
86+
87+
enum tf_err_t tf_read_var_header(const uint8_t *bd, int bs, struct tf_var_header_t *varHeader, uint8_t *vd, int vs, uint8_t **ep) {
88+
const int VAR_HEADER_SIZE = 4;
89+
90+
// variable header requires 4 bytes and is NULL terminated
91+
// an empty variable should be at least 5 bytes
92+
if (bs <= VAR_HEADER_SIZE) {
93+
return TF_EINVALID_VAR_HEADER_SIZE;
94+
} else {
95+
memcpy((unsigned char *) &varHeader->size, &bd[0], 4);
96+
97+
// only attempt to read variable value if a decoding buffer (`vd`) is provided
98+
// `.size` already includes the 4 bytes the header consumes
99+
if (vd != NULL) {
100+
const int valueSize = varHeader->size - VAR_HEADER_SIZE;
101+
102+
if (vs < valueSize) {
103+
return TF_EINVALID_VAR_VALUE_SIZE;
104+
} else {
105+
memcpy((unsigned char *) vd, &bd[VAR_HEADER_SIZE], valueSize);
106+
}
107+
}
108+
109+
if (ep != NULL) {
110+
*ep = ((uint8_t *) bd) + varHeader->size;
111+
}
112+
113+
return TF_OK;
114+
}
115+
}
116+
117+
static uint32_t tf_read_uint24(const uint8_t *bd) {
118+
// WARNING: this assumes little endian byte order
119+
return (uint32_t) (bd[0] & (bd[1] << 8) & (bd[2] << 16));
120+
}
121+
122+
enum tf_err_t tf_read_channel_range(const uint8_t *bd, int bs, struct tf_channel_range_t *channelRange, uint8_t **ep) {
123+
const int CHANNEL_RANGE_SIZE = 6;
124+
125+
if (bs < CHANNEL_RANGE_SIZE) {
126+
return TF_EINVALID_CHANNEL_RANGE_SIZE;
127+
} else {
128+
channelRange->firstChannelNumber = tf_read_uint24(&bd[0]);
129+
channelRange->channelCount = tf_read_uint24(&bd[3]);
130+
131+
if (ep != NULL) {
132+
*ep = ((uint8_t *) bd) + CHANNEL_RANGE_SIZE;
133+
}
134+
135+
return TF_OK;
136+
}
137+
}
138+
139+
float tf_sequence_duration_seconds(uint32_t frameCount, uint8_t frameStepTimeMillis) {
140+
const unsigned int millis = frameCount * frameStepTimeMillis;
141+
return ((float) millis) / 1000.0F;
142+
}

0 commit comments

Comments
 (0)