init push
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -174,3 +174,6 @@ cython_debug/
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
|
||||
# ...
|
||||
*.ARW
|
||||
|
||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
14
README.md
14
README.md
@@ -1,3 +1,15 @@
|
||||
# qifdp
|
||||
|
||||
QIFDP - Quick IFD Parser
|
||||
QIFDP - Quick IFD Parser
|
||||
|
||||
Tested with:
|
||||
- Sony ARW v4.0.1
|
||||
|
||||
# Build
|
||||
|
||||
If you don't already have uv installed: `pip install uv`
|
||||
|
||||
```
|
||||
uv build
|
||||
pip install dist/qifdp-0.1.0.tar.gz
|
||||
```
|
||||
19
examples/read_ifd_tags.py
Normal file
19
examples/read_ifd_tags.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from qifdp import IFDTagMap
|
||||
from qifdp import get_raw_ifd_tag
|
||||
|
||||
file_path = "DSC00001.ARW"
|
||||
|
||||
# Get the DateTimeOriginal
|
||||
date = get_raw_ifd_tag(file_path=file_path)
|
||||
print(date)
|
||||
|
||||
# Get the camera brand name
|
||||
make = get_raw_ifd_tag(file_path=file_path, tag_bytes=IFDTagMap.Make)
|
||||
print(make)
|
||||
|
||||
# If the buffer from 0x80000 (512k) inst enough for parsing the IFD tag,
|
||||
# you can increase the buffer. Use either a hex value or an int.
|
||||
# 0x80000 | 524288
|
||||
# 0x100000 | 1048576
|
||||
date = get_raw_ifd_tag(file_path=file_path, read_buffer=0x100000)
|
||||
print(date)
|
||||
32
pyproject.toml
Normal file
32
pyproject.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[project]
|
||||
name = "qifdp"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = []
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[metadata]
|
||||
name = "qidfp"
|
||||
version = "0.1.0"
|
||||
description = "A Python library to quickly parse IFD Tags"
|
||||
long_description = "file:README.md"
|
||||
long_description_content_type = "text/markdown"
|
||||
author = "DasMoorhuhn"
|
||||
author_email = "dasmoorhuhn@proton.me"
|
||||
license = "GPLv3.0"
|
||||
url = "https://github.com/yourusername/my-library"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
6
src/qifdp/__init__.py
Normal file
6
src/qifdp/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
A quick IFD parser
|
||||
"""
|
||||
|
||||
from .main import get_raw_ifd_tag
|
||||
from .main import IFDTagMap
|
||||
78
src/qifdp/main.py
Normal file
78
src/qifdp/main.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import struct
|
||||
|
||||
|
||||
class IFDTagMap:
|
||||
# Image tags
|
||||
ImageWidth = b'\x01\x00' # 0x0100
|
||||
ImageLength = b'\x01\x01' # 0x0101
|
||||
Make = b'\x01\x0F' # 0x010F
|
||||
Model = b'\x01\x10' # 0x0110
|
||||
Orientation = b'\x01\x12' # 0x0112
|
||||
XResolution = b'\x01\x1A' # 0x011A
|
||||
YResolution = b'\x01\x1B' # 0x011B
|
||||
ResolutionUnit = b'\x01\x28' # 0x0128
|
||||
Software = b'\x01\x31' # 0x0131
|
||||
DateTime = b'\x01\x32' # 0x0132
|
||||
Artist = b'\x01\xB0' # 0x01B0
|
||||
Copyright = b'\x82\x98' # 0x8298
|
||||
|
||||
# Exposure tags
|
||||
ExposureTime = b'\x82\x9A' # 0x829A
|
||||
FNumber = b'\x82\x9D' # 0x829D
|
||||
ISOSpeedRatings = b'\x88\x69' # 0x8827
|
||||
ExposureBiasValue = b'\x92\x73' # 0x9273
|
||||
MeteringMode = b'\x92\x7C' # 0x927C
|
||||
Flash = b'\x92\x19' # 0x9209
|
||||
FocalLength = b'\x92\x26' # 0x920A
|
||||
|
||||
# Date/Time tags
|
||||
DateTimeOriginal = b'\x90\x03' # 0x9003
|
||||
DateTimeDigitized = b'\x90\x04' # 0x9004
|
||||
SubsecTime = b'\x92\x00' # 0x9200
|
||||
SubsecTimeOriginal = b'\x92\x01' # 0x9201
|
||||
SubsecTimeDigitized = b'\x92\x02' # 0x9202
|
||||
|
||||
# Image dimensions and color
|
||||
ColorSpace = b'\xA0\x13' # 0xA001
|
||||
PixelXDimension = b'\xA0\x02' # 0xA002
|
||||
PixelYDimension = b'\xA0\x03' # 0xA003
|
||||
|
||||
# Camera-specific / MakerNote
|
||||
CameraOwnerName = b'\xC6\x0E' # 0xC60E
|
||||
|
||||
|
||||
def get_raw_ifd_tag(file_path:str, read_buffer:int=0x80000, tag_bytes:bytes=IFDTagMap.DateTimeOriginal) -> str:
|
||||
with open(file_path, "rb") as f:
|
||||
f.seek(0, 2)
|
||||
size_bytes = f.tell()
|
||||
read_buffer = min(read_buffer, size_bytes)
|
||||
f.seek(0)
|
||||
data = f.read(read_buffer)
|
||||
|
||||
# Try first LE then BE
|
||||
offset = data.find(tag_bytes[::-1])
|
||||
endian = "<"
|
||||
if offset == -1:
|
||||
offset = data.find(tag_bytes)
|
||||
endian = ">"
|
||||
|
||||
if offset == -1:
|
||||
raise ValueError("Tag 0x9003 not found in the file.")
|
||||
|
||||
# Read type and count
|
||||
type_, count = struct.unpack(endian + "HI", data[offset+2:offset+8])
|
||||
|
||||
# Read value/offset
|
||||
value_or_offset = struct.unpack(endian + "I", data[offset+8:offset+12])[0]
|
||||
|
||||
# If ASCII and count > 4, value_or_offset is the file offset of the string
|
||||
if type_ == 2 and count > 4:
|
||||
value_offset = value_or_offset
|
||||
value_bytes = data[value_offset:value_offset+count]
|
||||
value = value_bytes.decode('ascii')
|
||||
else:
|
||||
# Small values stored inline
|
||||
value_bytes = data[offset+8:offset+8+count]
|
||||
value = value_bytes.decode('ascii')
|
||||
|
||||
return value
|
||||
Reference in New Issue
Block a user