Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FYI: filename encoding documented - full list of flags #72

Open
sven337 opened this issue Aug 13, 2024 · 13 comments
Open

FYI: filename encoding documented - full list of flags #72

sven337 opened this issue Aug 13, 2024 · 13 comments

Comments

@sven337
Copy link

sven337 commented Aug 13, 2024

Hello,
FYI, I looked at the Reolink FW and figured out the encoding of the file names in its entirety.
Of interest in particular would be the list of attributes:
https://github.com/sven337/ReolinkLinux/wiki/Figuring-out-the-file-names#attributes

@starkillerOG
Copy link
Owner

@sven337 that is very instresting, thanks a lot for sharing!
I will defenitly look into this to improve the Reolink itegration.
At the moment a bit bussy with some other Reolink imporvements, but will keep this issue open untill I find some time to work on it.

Did you perhaps also look into the file names of the Reolink Home Hub?
That uses a even newer file naming standard from which I did not figure out what the flags mean....

@sven337
Copy link
Author

sven337 commented Aug 16, 2024

Let me know where to find the firmware and show me the filename pattern, and I may be able to figure it out.

@starkillerOG
Copy link
Owner

The firmware is on the reolink download center: https://reolink.com/download-center/?srsltid=AfmBOor3BU5F6iWv9p1mMWavhnPrtjGFN2QigYLOH4eaepdvwtnU-eMS

Device name: Reolink Home Hu
Hardware version: BASE_WENNT6NA5

I will give you a filenmae pattern as soon as possible, I don't have my Home Hub plugged in right now and am working on way too many issues at the same time at the moment...

@sven337
Copy link
Author

sven337 commented Aug 20, 2024

Thanks, I have the firmware bits, and it looks like the relevant functions are indeed different.
I need to see the pattern to help with RE. Please tell me everything you know/have and what you're missing, and I should be able to RE that.

As for the flags from the camera, here's how I'm decoding them:
https://github.com/ReolinkCameraAPI/reolinkapipy/blob/master/examples/video_review_gui.py#L28
feel free to reuse

@starkillerOG
Copy link
Owner

@sven337 sorry for the delay, this is the filename of a recording from the Reolink Home Hub (I replaced the UID and camera name):
"/mnt/sda/<UID>-<NAME>/Mp4Record/2024-08-27/RecM02_DST20240827_090302_090334_0_800_800_033C820000_61B6F0.mp4"

First I thought the last 6 chars would be the tags so I made a list of those tags for diffrent events:

21DCCD - 001000011101110011001101 - pet + person
182721 - 000110000010011100100001 - person
18DF13 - 000110001101111100010011 - motion + person
188FF9 - 000110001000111111111001 - pet
17DD6E - 000101111101110101101110 - pet
156701 - 000101010110011100000001 - motion
1F25E5 - 000111110010010111100101 - motion

But I could not find a logical pattern in this....

@starkillerOG
Copy link
Owner

I will defenitely use the decoding you wrote, it looks way nicer.
May take a while untill I get to it though....
Lots of things I want to implement and improve.

@starkillerOG
Copy link
Owner

For a Argus Track (dual lens camera) the recording on the Home Hub has the following names:

RecC02_DST20240623_163915_163926_0_380_200_033C800000_8655E.mp4
RecE02_DST20240623_163915_163925_0_780_438_033C820000_F6362.mp4
RecM02_DST20240623_163915_163924_0_F00_870_033C820000_34B93A.mp4
RecS02_DST20240623_163915_163924_0_380_200_033C800000_7EB2A.mp4

For the sub/main stream of both lenses

@sven337
Copy link
Author

sven337 commented Aug 27, 2024

Here are my findings: https://github.com/sven337/ReolinkLinux/wiki/Figuring-out-the-file-names#filename-structure-for-reolink-home-hub
as far as I can tell and just like for camera, the last hex group is the filesize!

In my link you can find the flags you are looking for:

flags_mapping_v2 = {
        'resolution_index': (32,    7),
        'tv_system': (31,           1),
        'framerate': (24,           7),
        'audio_index': (22,         2),
        'ai_pd': (21,               1),  # person detection
        'ai_fd': (20,               1),  # face detection
        'ai_vd': (19,               1),  # vehicle detection
        'ai_ad' : (18 ,             1),
        'ai_other' : (16,           2), 
        'encoder_type_index': (15,  1),
        'is_schedule_record' : (14, 1),
        'is_motion_record': (13,    1),
        'is_rf_record': (12,        1),
        'is_doorbell_record': (11,  1),
        'picture_layout_index': (4, 7),
        'package_delivered' : (3,   1),
        'package_takenaway': (2,    1),
        'package_event' : (1,       1),
        'upload_flag' : (0,         1),
}```

@starkillerOG
Copy link
Owner

@sven337 thank you very much for figuring this all out, I wrote some initial code here:

def parse_file_name(file_name: str, tzInfo: Optional[dtc.tzinfo] = None) -> Parsed_VOD_file_name | None:
# Mp4Record/2023-04-26/RecS02_DST20230426_145918_150032_2B14808_32F1DF.mp4
# Mp4Record/2020-12-22/RecM01_20201222_075939_080140_6D28808_1A468F9.mp4
# "/mnt/sda/<UID>-<NAME>/Mp4Record/2024-08-27/RecM02_DST20240827_090302_090334_0_800_800_033C820000_61B6F0.mp4"
# https://github.com/sven337/ReolinkLinux/wiki/Figuring-out-the-file-names
(path_name, ext) = file_name.rsplit(".", 2)
name = path_name.rsplit("/", 1)[-1]
split = name.split("_")
if not split[0].startswith("Rec") or len(split[0]) != 6:
_LOGGER.debug("%s does not match known formats, could not find version", file_name)
return None
version = int(split[0][5])
if len(split) == 6:
# RecM01_20201222_075939_080140_6D28808_1A468F9
(_, start_date, start_time, end_time, hex_value, filesize) = split
elif len(split) == 9:
# RecM02_DST20240827_090302_090334_0_800_800_033C820000_61B6F0
(_, start_date, start_time, end_time, animal_type, width, height, hex_value, filesize) = split
else:
_LOGGER.debug("%s does not match known formats, unknown length", file_name)
return None
if version == 2 and len(hex_value) != 10:
version = 3
flag_values = decode_hex_to_flags(hex_value, version)
triggers = VOD_trigger.NONE
if flag_values["ai_pd"]:
triggers |= VOD_trigger.PERSON
if flag_values["ai_vd"]:
triggers |= VOD_trigger.VEHICLE
if flag_values["ai_ad"]:
triggers |= VOD_trigger.ANIMAL
if flag_values["is_schedule_record"]:
triggers |= VOD_trigger.TIMER
if flag_values["is_motion_record"]:
triggers |= VOD_trigger.MOTION
if flag_values["is_doorbell_record"]:
triggers |= VOD_trigger.DOORBELL
if flag_values.get("package_event"):
triggers |= VOD_trigger.PACKAGE
start_date = start_date.lower().replace("dst", "")
start = dtc.datetime.strptime(start_date + start_time, "%Y%m%d%H%M%S").replace(tzinfo=tzInfo)
end = dtc.datetime.strptime(start_date + end_time, "%Y%m%d%H%M%S").replace(tzinfo=tzInfo) if end_time != "000000" else start
return Parsed_VOD_file_name(name, ext, start.date(), start.time(), end.time(), triggers)
def decode_hex_to_flags(hex_value: str, version: int) -> dict[str, int]:
hex_value = int(hex_value, 16)
flag_values = {}
for flag, (bit_position, bit_size) in FLAGS_MAPPING[version].items():
mask = ((1 << bit_size) - 1) << bit_position
flag_values[flag] = (hex_value & mask) >> bit_position
return flag_values
FLAGS_MAPPING = {
0 : { # Version 0
'resolution_index': (30, 7),
'tv_system': (29, 1),
'framerate': (22, 7),
'audio_index': (20, 2),
'ai_pd': (19, 1), # person detection
'ai_fd': (18, 1), # face detection
'ai_vd': (17, 1), # vehicle detection
'ai_ad': (16, 1), # animal detection
'encoder_type_index': (14, 2),
'is_schedule_record': (13, 1),
'is_motion_record': (12, 1),
'is_rf_record': (11, 1),
'is_doorbell_record': (10, 1),
'is_ai_other_record': (9, 1),
'picture_layout_index': (2, 7),
'package_delivered': (1, 1),
'package_takenaway': (0, 1),
},
1: { # Version 1
'resolution_index': (31, 7),
'tv_system': (30, 1),
'framerate': (23, 7),
'audio_index': (21, 2),
'ai_pd': (20, 1), # person detection
'ai_fd': (19, 1), # face detection
'ai_vd': (18, 1), # vehicle detection
'ai_ad': (17, 1), # animal detection
'encoder_type_index': (15, 2),
'is_schedule_record': (14, 1),
'is_motion_record': (13, 1),
'is_rf_record': (12, 1),
'is_doorbell_record': (11, 1),
'is_ai_other_record': (10, 1),
'picture_layout_index': (3, 7),
'package_delivered': (2, 1),
'package_takenaway': (1, 1),
'package_event': (0, 1)
},
2 : { # Version 2
'resolution_index': (32, 7),
'tv_system': (31, 1),
'framerate': (24, 7),
'audio_index': (22, 2),
'ai_pd': (21, 1), # person detection
'ai_fd': (20, 1), # face detection
'ai_vd': (19, 1), # vehicle detection
'ai_ad' : (18, 1), # animal detection
'ai_other' : (16, 2),
'encoder_type_index': (15, 1),
'is_schedule_record' : (14, 1),
'is_motion_record': (13, 1),
'is_rf_record': (12, 1),
'is_doorbell_record': (11, 1),
'picture_layout_index': (4, 7),
'package_delivered' : (3, 1),
'package_takenaway': (2, 1),
'package_event' : (1, 1),
'upload_flag' : (0, 1),
},
3 : { # Version 3
'resolution_index': (21, 7),
'tv_system': (20, 1),
'framerate': (13, 7),
'audio_index': (11, 2),
'ai_pd': (10, 1), # person detection
'ai_fd': (9, 1), # face detection
'ai_vd': (8, 1), # vehicle detection
'ai_ad': (7, 1), # animal detection
'encoder_type_index': (5, 2),
'is_schedule_record': (4, 1),
'is_motion_record': (3, 1),
'is_rf_record': (2, 1),
'is_doorbell_record': (1, 1),
'ai_other': (0, 1)
},
}

It works on "normal" Reolink cams, but on the Home Hub it does not work.
I noticed the hex_flag is always 033C800000 no matter what type of events triggered the recording.
This may be a firmware bug in the Home Hub though.... not sure.

@sven337
Copy link
Author

sven337 commented Aug 28, 2024

As far as I can tell your implementation of flags isn't quite correct: the home hub and cameras use different flags.
Version 2 and 3 (I have both on my Trackmix, 3 is after the recent firmware upgrade) on cameras differ only by is_doorbell_press_record.
In fact in my table you can track all 3 known versions of flags on cameras.

The table of versions for the hub is different (it's a different namespace, in a way).

Your flag is sometimes 0x33C820000 in your own examples, which IIRC corresponds to ai_other (don't ask me what that is! it's not animal, not face, not vehicle and not person....). But I agree it is strange that they all use the same.
The resolution index being the same value despite resolution being obviously different is also a ????, but I am reasonably certain I got this right. (After checking, I am confused and need to figure this out, sorry for the noise)

Can you share more examples of hub filenames please, along with framerates for each video?
It seems that resolution index is supposed to be hardcoded to 1, yet my decoder sees 3. I've been over the list of flags in the code and don't see what I would have forgotten, but decoding 0x33C820000 as the following makes more sense:

{'resolution_index': 1,
 'tv_system': 1,
 'framerate': 30,
 'audio_index': 1,

since I'm reasonably certain your videos are not at 60FPS like originally decoded. :) Though even 30 seems like a lot.

FPS info would therefore help me align things. I do not know why you don't get trigger data.

@sven337
Copy link
Author

sven337 commented Sep 9, 2024

Note, the cameras have been updated to a new version of the encoding, version 9.
It is not the same as the home hub (!!!).
Support for the new encoding is here:
https://github.com/sven337/reolinkapipy/blob/af748158785774c556d4b799445103421bd757c3/examples/video_review_gui.py#L53

I haven't yet decoded the extra set of flags, they're always zero on my camera anyway so I do not care much. Probably same flags as the home hub (package events).

@starkillerOG
Copy link
Owner

@sven337 I have updated the decoding of the recording names in the reolink_aio lib:

if version not in FLAGS_MAPPING[dev_type]:
new_version = max(FLAGS_MAPPING[dev_type].keys())
_LOGGER.debug("%s has version %s, with hex lenght %s which is not yet known, using version %s instead", file_name, version, len(hex_value), new_version)
version = new_version
if len(hex_value) != FLAGS_LENGTH[dev_type].get(version, 0):
_LOGGER.debug("%s with version %s has unexpected hex lenght %s, expected %s", file_name, version, len(hex_value), FLAGS_LENGTH[dev_type].get(version, 0))
flag_values = decode_hex_to_flags(hex_value, version, dev_type)
triggers = VOD_trigger.NONE
if flag_values["ai_pd"]:
triggers |= VOD_trigger.PERSON
if flag_values["ai_vd"]:
triggers |= VOD_trigger.VEHICLE
if flag_values["ai_ad"]:
triggers |= VOD_trigger.ANIMAL
if flag_values["is_schedule_record"]:
triggers |= VOD_trigger.TIMER
if flag_values["is_motion_record"]:
triggers |= VOD_trigger.MOTION
if flag_values["is_doorbell_record"]:
triggers |= VOD_trigger.DOORBELL
if flag_values.get("package_event"):
triggers |= VOD_trigger.PACKAGE
start_date = start_date.lower().replace("dst", "")
start = dtc.datetime.strptime(start_date + start_time, "%Y%m%d%H%M%S").replace(tzinfo=tzInfo)
end = dtc.datetime.strptime(start_date + end_time, "%Y%m%d%H%M%S").replace(tzinfo=tzInfo) if end_time != "000000" else start
return Parsed_VOD_file_name(name, ext, start.date(), start.time(), end.time(), triggers)
def decode_hex_to_flags(hex_value: str, version: int, dev_type: str) -> dict[str, int]:
hex_int = int(hex_value, 16)
hex_int_rev = int(bin(hex_int)[2:].zfill(len(hex_value)*4)[::-1],2) # reverse the binary
flag_values = {}
for flag, (bit_position, bit_size) in FLAGS_MAPPING[dev_type][version].items():
mask = ((1 << bit_size) - 1) << bit_position
flag_val_rev = (hex_int_rev & mask) >> bit_position
flag_values[flag] = int(bin(flag_val_rev)[2:].zfill(bit_size)[::-1],2) # reverse the segment back
return flag_values
FLAGS_CAM_V2 = {
"resolution_index": (0, 7),
"tv_system": (7, 1),
"framerate": (8, 7),
"audio_index": (15, 2),
"ai_pd": (17, 1), # person detection
"ai_fd": (18, 1), # face detection
"ai_vd": (19, 1), # vehicle detection
"ai_ad": (20, 1), # animal detection
"encoder_type_index": (21, 2),
"is_schedule_record": (23, 1),
"is_motion_record": (24, 1),
"is_rf_record": (25, 1),
"is_doorbell_record": (26, 1),
"ai_other": (27, 1),
}
FLAGS_HUB_V0 = {
"resolution_index": (0, 7),
"tv_system": (7, 1),
"framerate": (8, 7),
"audio_index": (15, 2),
"ai_pd": (17, 1), # person detection
"ai_fd": (18, 1), # face detection
"ai_vd": (19, 1), # vehicle detection
"ai_ad": (20, 1), # animal detection
"encoder_type_index": (21, 2),
"is_schedule_record": (23, 1),
"is_motion_record": (24, 1),
"is_rf_record": (25, 1),
"is_doorbell_record": (26, 1),
"is_ai_other_record": (27, 1),
"picture_layout_index": (28, 7),
"package_delivered": (35, 1),
"package_takenaway": (36, 1),
}
FLAGS_HUB_V1 = FLAGS_HUB_V0
FLAGS_HUB_V1.update({
"package_event": (37, 1),
})
FLAGS_LENGTH = {
"cam": {
2: 7, # Version 2
3: 7, # Version 3
4: 9, # Version 4
9: 14, # Version 9
},
"hub": {
2: 10, # Version 2
},
}
FLAGS_MAPPING = {
"cam": {
2: FLAGS_CAM_V2,
3: FLAGS_CAM_V2,
4: FLAGS_CAM_V2,
9: FLAGS_CAM_V2,
},
"hub": {
0: FLAGS_HUB_V0,
1: FLAGS_HUB_V1,
2: { # Version 2
"resolution_index": (0, 7),
"tv_system": (7, 1),
"framerate": (8, 7),
"audio_index": (15, 2),
"ai_pd": (17, 1), # person detection
"ai_fd": (18, 1), # face detection
"ai_vd": (19, 1), # vehicle detection
"ai_ad": (20, 1), # animal detection
"ai_other": (21, 2),
"encoder_type_index": (23, 1),
"is_schedule_record": (24, 1),
"is_motion_record": (25, 1),
"is_rf_record": (26, 1),
"is_doorbell_record": (27, 1),
"picture_layout_index": (28, 7),
"package_delivered": (35, 1),
"package_takenaway": (36, 1),
"package_event": (37, 1),
"upload_flag": (38, 1),
},
},
}

I think it should now work a bit better.

The most noticible improvement I have made is to reverse the binary hex value and adjust all mappings acordingly. That makes sure the first bit accross all versions is always the resolution_index and so on, newer version just append more stuff at the higher bits.

Therefore the offset does not need to be adjusted between versions anymore.

hex_int_rev = int(bin(hex_int)[2:].zfill(len(hex_value)*4)[::-1],2) # reverse the binary

flag_values[flag] = int(bin(flag_val_rev)[2:].zfill(bit_size)[::-1],2) # reverse the segment back

I am not so good in all the binary operations, so this can proably be done more ellegantly, faster and more compact, but in any case this works and should make the decoding a lot more resilient against future version changes.

@sven337
Copy link
Author

sven337 commented Sep 14, 2024

Nice job, it is better to have flipped things indeed. My version is the product of the reverse engineering but I wasn't exactly focused on doing something nice either, I only wanted something that worked :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants