Skip to content

Commit ef72ea0

Browse files
committed
Parse fseq headers correctly
1 parent f6836e6 commit ef72ea0

File tree

3 files changed

+162
-3
lines changed

3 files changed

+162
-3
lines changed

src/fseq/__init__.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,142 @@
11
__version__ = '0.1.0'
2+
3+
import zstandard as zstd
4+
5+
6+
class ParserError(Exception):
7+
pass
8+
9+
10+
def int_from_bytes(bytes):
11+
# TODO: only valid in python 3.2+
12+
return int.from_bytes(bytes, 'little')
13+
14+
15+
def compression_type_from_num(n):
16+
if n == 0:
17+
return None
18+
if n == 1:
19+
return 'zstd'
20+
if n == 2:
21+
return 'gzip'
22+
raise ParserError('unrecognized compression type: %d' % n)
23+
24+
25+
class Fseq:
26+
def __init__(
27+
self,
28+
file,
29+
minor_version,
30+
major_version,
31+
channel_data_start,
32+
channel_count_per_frame,
33+
number_of_frames,
34+
step_time_in_ms,
35+
unique_id,
36+
compression_type,
37+
compression_blocks,
38+
sparse_ranges,
39+
variable_headers):
40+
41+
self.file = file
42+
self.minor_version = minor_version
43+
self.major_version = major_version
44+
self.version = (major_version, minor_version)
45+
46+
self.channel_data_start = channel_data_start
47+
self.channel_count_per_frame = channel_count_per_frame
48+
self.number_of_frames = number_of_frames
49+
self.step_time_in_ms = step_time_in_ms
50+
self.unique_id = unique_id
51+
self.compression_type = compression_type
52+
self.compression_blocks = compression_blocks
53+
self.sparse_ranges = sparse_ranges
54+
self.variable_headers = variable_headers
55+
56+
def frame_at(self, index):
57+
if index >= self.number_of_frames:
58+
raise ValueError('frame index out of bounds')
59+
60+
offset = self.channel_data_start + index * self.channel_count_per_frame
61+
self.file.seek(offset, 0)
62+
# data = self.file.read(self.channel_count_per_frame)
63+
# return [b for b in data]
64+
65+
66+
def parse(f):
67+
magic = f.read(4)
68+
if magic != b'PSEQ':
69+
raise ParserError('invalid fseq file magic')
70+
71+
channel_data_start = int_from_bytes(f.read(2))
72+
73+
minor_version = int_from_bytes(f.read(1))
74+
major_version = int_from_bytes(f.read(1))
75+
76+
version = (major_version, minor_version)
77+
if version != (2, 0):
78+
raise ParserError('unrecognized fseq file version: %s' % version)
79+
80+
standard_header_length = int_from_bytes(f.read(2))
81+
82+
channel_count_per_frame = int_from_bytes(f.read(4))
83+
84+
number_of_frames = int_from_bytes(f.read(4))
85+
86+
step_time_in_ms = int_from_bytes(f.read(1))
87+
88+
bit_flags = int_from_bytes(f.read(1))
89+
if bit_flags != 0:
90+
raise ParserError('unrecognized bit flags: %d' % bit_flags)
91+
92+
compression_type = compression_type_from_num(int_from_bytes(f.read(1)))
93+
num_compression_blocks = int_from_bytes(f.read(1))
94+
num_sparse_ranges = int_from_bytes(f.read(1))
95+
96+
bit_flags = int_from_bytes(f.read(1))
97+
if bit_flags != 0:
98+
raise ParserError('unrecognized bit flags: %d' % bit_flags)
99+
100+
unique_id = f.read(8)
101+
102+
compression_blocks = []
103+
for i in range(num_compression_blocks):
104+
frame_number = int_from_bytes(f.read(4))
105+
length_of_block = int_from_bytes(f.read(4))
106+
compression_blocks.append((frame_number, length_of_block))
107+
108+
109+
sparse_ranges = []
110+
for i in range(num_sparse_ranges):
111+
start_channel_number = int_from_bytes(f.read(3))
112+
number_of_channels = int_from_bytes(f.read(3))
113+
sparse_ranges.append((start_channel_number, number_of_channels))
114+
115+
variable_headers = []
116+
start = f.tell()
117+
118+
while start < channel_data_start - 4:
119+
length = int_from_bytes(f.read(2))
120+
if length == 0:
121+
break
122+
123+
vheader_code = f.read(2).decode('ascii')
124+
vheader_data = f.read(length - 4)
125+
variable_headers.append((vheader_code, vheader_data))
126+
127+
start += length
128+
129+
return Fseq(
130+
file=f,
131+
minor_version=minor_version,
132+
major_version=major_version,
133+
channel_data_start=channel_data_start,
134+
channel_count_per_frame=channel_count_per_frame,
135+
number_of_frames=number_of_frames,
136+
step_time_in_ms=step_time_in_ms,
137+
unique_id=unique_id,
138+
compression_type=compression_type,
139+
compression_blocks=compression_blocks,
140+
sparse_ranges=sparse_ranges,
141+
variable_headers=variable_headers,
142+
)

tests/test.fseq

28.4 MB
Binary file not shown.

tests/test_fseq.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
1-
2-
from fseq import main
1+
import os
2+
from fseq import parse
33

44

55
def test_main():
6-
pass
6+
path = os.path.join(os.path.dirname(__file__), 'test.fseq')
7+
fseq_obj = parse(open(path, 'rb'))
8+
9+
assert fseq_obj is not None
10+
assert fseq_obj.version == (2, 0)
11+
assert fseq_obj.channel_data_start == 2144
12+
assert fseq_obj.channel_count_per_frame == 17372
13+
assert fseq_obj.number_of_frames == 9563
14+
assert fseq_obj.step_time_in_ms == 25
15+
assert fseq_obj.compression_type == 'zstd'
16+
assert fseq_obj.unique_id == b'\xb0\xf2\xb8\xb3g\x82\x05\x00'
17+
assert fseq_obj.variable_headers == [
18+
('mf', b'/Users/nico/Domencia/multimedia files/the-chemical-brothers-star-guitar.mp3\0')
19+
]
20+
assert len(fseq_obj.compression_blocks) > 0
21+
assert fseq_obj.sparse_ranges == []
22+
23+
24+
assert fseq_obj.frame_at(0) is not None

0 commit comments

Comments
 (0)