From aabecd547f67d1b4128e5449bad0c1fd86ed0818 Mon Sep 17 00:00:00 2001 From: HERITAGE-XION Date: Wed, 7 Jan 2026 14:38:16 +0100 Subject: [PATCH] metadata-scrubber-tool: implemented fix mypy type checking issues for piexif(it has no stub files) used basedpyright to generate a type stub, but damn that a lot more type related issues fixed most but a few remained so i modified the pyrightconfig with this: "typeCheckingMode": "standard" as it is in strict mode by default. --- .env.example | 5 - pyrightconfig.json | 8 + src/commands/read.py | 2 +- src/core/jpeg_metadata.py | 28 ++- src/core/png_metadata.py | 16 +- src/main.py | 8 +- src/services/batch_processor.py | 24 +-- src/services/image_handler.py | 23 +-- src/services/metadata_handler.py | 10 +- src/utils/display.py | 4 +- src/utils/get_target_files.py | 2 +- typings/piexif/__init__.pyi | 15 ++ typings/piexif/_common.pyi | 24 +++ typings/piexif/_dump.pyi | 20 ++ typings/piexif/_exceptions.pyi | 8 + typings/piexif/_exif.pyi | 341 +++++++++++++++++++++++++++++++ typings/piexif/_insert.pyi | 17 ++ typings/piexif/_load.pyi | 32 +++ typings/piexif/_remove.pyi | 16 ++ typings/piexif/_transplant.pyi | 17 ++ typings/piexif/_webp.pyi | 28 +++ typings/piexif/helper.pyi | 44 ++++ 22 files changed, 635 insertions(+), 57 deletions(-) delete mode 100644 .env.example create mode 100644 pyrightconfig.json create mode 100644 typings/piexif/__init__.pyi create mode 100644 typings/piexif/_common.pyi create mode 100644 typings/piexif/_dump.pyi create mode 100644 typings/piexif/_exceptions.pyi create mode 100644 typings/piexif/_exif.pyi create mode 100644 typings/piexif/_insert.pyi create mode 100644 typings/piexif/_load.pyi create mode 100644 typings/piexif/_remove.pyi create mode 100644 typings/piexif/_transplant.pyi create mode 100644 typings/piexif/_webp.pyi create mode 100644 typings/piexif/helper.pyi diff --git a/.env.example b/.env.example deleted file mode 100644 index 6a81f92b..00000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# Environment Variables Example -# Copy this file to .env and fill in your values - -# Example: -# API_KEY=your_api_key_here diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..848796cb --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,8 @@ +{ + "stubPath": "./typings", + "reportMissingTypeStubs": false, + "venvPath": ".", + "venv": ".venv", + "pythonVersion": "3.12", + "typeCheckingMode": "standard", +} diff --git a/src/commands/read.py b/src/commands/read.py index 1c2d3ba5..01ba37ff 100644 --- a/src/commands/read.py +++ b/src/commands/read.py @@ -36,7 +36,7 @@ def read( raise typer.BadParameter("If you provide --recursive or -r, you must also provide --extension or -ext.") if ext and not recursive: raise typer.BadParameter("If you provide --extension or -ext, you must also provide --recursive or -r.") - + for file in get_target_files(file_path, ext) if recursive else [file_path]: try: # Get the correct object from the factory diff --git a/src/core/jpeg_metadata.py b/src/core/jpeg_metadata.py index 1b4f3c41..29962176 100644 --- a/src/core/jpeg_metadata.py +++ b/src/core/jpeg_metadata.py @@ -5,10 +5,23 @@ This module provides the JpegProcessor class which handles EXIF metadata extraction and manipulation for JPEG images using the piexif library. """ -import piexif # pyright: ignore[reportMissingTypeStubs] +from typing import TypedDict + +import piexif +from PIL import Image from src.utils.exceptions import MetadataNotFoundError, MetadataProcessingError +# Type alias for EXIF tag values (strings, bytes, ints, tuples, or lists) +ExifValue = str | bytes | int | tuple[object, ...] | list[object] + + +class JpegMetadataResult(TypedDict): + """Return type for JpegProcessor.get_metadata().""" + + data: dict[str, ExifValue] + tags_to_delete: list[int] + class JpegProcessor: """ @@ -24,10 +37,10 @@ class JpegProcessor: def __init__(self): """Initialize the JPEG processor with empty data structures.""" - self.tags_to_delete = [] - self.data = {} + self.tags_to_delete: list[int] = [] + self.data: dict[str, ExifValue] = {} - def get_metadata(self, img): + def get_metadata(self, img: Image.Image) -> JpegMetadataResult: """ Extract EXIF metadata from a JPEG image. @@ -51,10 +64,11 @@ class JpegProcessor: # Iterate through the IFD for tag, tag_value in exif_dict[ifd].items(): - tag_info = piexif.TAGS[ifd].get(tag, {}) + ifd_tags = piexif.TAGS.get(ifd, {}) + tag_info = ifd_tags.get(tag, {}) if isinstance(ifd_tags, dict) else {} # Get the human-readable name for the tag - tag_name = ( + tag_name = str( tag_info.get("name", "Unknown Tag") if tag_info else "Unknown Tag" ) @@ -72,7 +86,7 @@ class JpegProcessor: return {"data": self.data, "tags_to_delete": self.tags_to_delete} - def delete_metadata(self, img, tags_to_delete): + def delete_metadata(self, img: Image.Image, tags_to_delete: list[int]): """ Remove specified EXIF tags from a JPEG image. diff --git a/src/core/png_metadata.py b/src/core/png_metadata.py index f240a472..2362d7b5 100644 --- a/src/core/png_metadata.py +++ b/src/core/png_metadata.py @@ -6,7 +6,7 @@ extraction and manipulation for PNG images, including both EXIF data and PNG textual metadata (PngInfo chunks). """ -from typing import Any, Dict, List, Optional +from typing import Any from PIL import ExifTags, PngImagePlugin from PIL.Image import Exif, Image @@ -44,11 +44,11 @@ class PngProcessor: def __init__(self): """Initialize the PNG processor with empty data structures.""" - self.tags_to_delete: List[int] = [] - self.text_keys_to_delete: List[str] = [] - self.data: Dict[str, Any] = {} + self.tags_to_delete: list[int] = [] + self.text_keys_to_delete: list[str] = [] + self.data: dict[str, Any] = {} - def get_metadata(self, img: Image) -> Dict[str, Any]: + def get_metadata(self, img: Image) -> dict[str, Any]: """ Extract metadata from a PNG image. @@ -112,7 +112,7 @@ class PngProcessor: "text_keys": self.text_keys_to_delete, } - def delete_metadata(self, img: Image, tags_to_delete: List[int]) -> Exif: + def delete_metadata(self, img: Image, tags_to_delete: list[int]) -> Exif: """ Remove EXIF tags from a PNG image. @@ -143,8 +143,8 @@ class PngProcessor: raise MetadataProcessingError(f"Error processing PNG EXIF: {str(e)}") def get_clean_pnginfo( - self, img: Image, keys_to_remove: Optional[List[str]] = None - ) -> Optional[PngImagePlugin.PngInfo]: + self, img: Image, keys_to_remove: list[str] | None = None + ) -> PngImagePlugin.PngInfo | None: """ Create a new PngInfo with sensitive keys removed. diff --git a/src/main.py b/src/main.py index 3c8d98a9..2c5543c8 100644 --- a/src/main.py +++ b/src/main.py @@ -50,10 +50,10 @@ def main( help="Show detailed debug logs for every file processed.", ), version: bool = typer.Option( - None, - "--version", - "-v", - callback=version_callback, + None, + "--version", + "-v", + callback=version_callback, is_eager=True, help="Show the application version and exit." ), diff --git a/src/services/batch_processor.py b/src/services/batch_processor.py index a4ad2d0f..0f4543f2 100644 --- a/src/services/batch_processor.py +++ b/src/services/batch_processor.py @@ -10,10 +10,10 @@ of large batches (1000+ files). import logging import threading +from collections.abc import Callable, Iterable from concurrent.futures import ThreadPoolExecutor, as_completed from dataclasses import dataclass, field from pathlib import Path -from typing import Callable, Iterable, List, Optional from rich.console import Console @@ -30,8 +30,8 @@ class FileResult: filepath: Path success: bool action: str # "scrubbed", "skipped", "dry-run" - output_path: Optional[Path] = None - error: Optional[str] = None + output_path: Path | None = None + error: str | None = None @dataclass @@ -43,8 +43,8 @@ class BatchSummary: skipped: int = 0 failed: int = 0 dry_run: bool = False - output_dir: Optional[Path] = None - results: List[FileResult] = field(default_factory=list) + output_dir: Path | None = None + results: list[FileResult] = field(default_factory=list) class BatchProcessor: @@ -58,7 +58,7 @@ class BatchProcessor: def __init__( self, - output_dir: Optional[str] = None, + output_dir: str | None = None, dry_run: bool = False, max_workers: int = 4, ): @@ -73,7 +73,7 @@ class BatchProcessor: self.output_dir = Path(output_dir) if output_dir else Path("./scrubbed") self.dry_run = dry_run self.max_workers = max_workers - self.results: List[FileResult] = [] + self.results: list[FileResult] = [] # Thread synchronization self._path_lock = threading.Lock() # Protects unique path generation @@ -92,7 +92,7 @@ class BatchProcessor: Returns: FileResult with success status and details. """ - output_path: Optional[Path] = None # Track reserved path for cleanup + output_path: Path | None = None # Track reserved path for cleanup try: # Dry-run mode: just report what would happen @@ -151,8 +151,8 @@ class BatchProcessor: def process_batch( self, files: Iterable[Path], - progress_callback: Optional[Callable[[FileResult], None]] = None, - ) -> List[FileResult]: + progress_callback: Callable[[FileResult], None] | None = None, + ) -> list[FileResult]: """ Process multiple files concurrently using ThreadPoolExecutor. @@ -184,7 +184,7 @@ class BatchProcessor: return self.results - def process_batch_sequential(self, files: Iterable[Path]) -> List[FileResult]: + def process_batch_sequential(self, files: Iterable[Path]) -> list[FileResult]: """ Process files sequentially (legacy behavior for debugging). @@ -228,7 +228,7 @@ class BatchProcessor: with self._results_lock: self.results.append(result) - def _cleanup_reserved_path(self, output_path: Optional[Path]) -> None: + def _cleanup_reserved_path(self, output_path: Path | None) -> None: """ Remove empty placeholder file created during path reservation. diff --git a/src/services/image_handler.py b/src/services/image_handler.py index ede104a9..07d52a57 100644 --- a/src/services/image_handler.py +++ b/src/services/image_handler.py @@ -8,7 +8,7 @@ format-specific processors (JpegProcessor, PngProcessor). import shutil from pathlib import Path -from typing import Optional +from typing import Any, cast import piexif # pyright: ignore[reportMissingTypeStubs] from PIL import Image @@ -48,13 +48,13 @@ class ImageHandler(MetadataHandler): filepath: Path to the image file to process. """ super().__init__(filepath) - self.processors = { + self.processors: dict[str, JpegProcessor | PngProcessor] = { "jpeg": JpegProcessor(), "png": PngProcessor(), } - self.tags_to_delete = [] - self.detected_format: Optional[str] = None - self.text_keys_to_delete = [] + self.tags_to_delete: list[int] = [] + self.detected_format: str | None = None + self.text_keys_to_delete: list[str] = [] def _detect_format(self) -> str: """ @@ -101,7 +101,8 @@ class ImageHandler(MetadataHandler): self.metadata = result["data"] self.tags_to_delete = result["tags_to_delete"] # Store text keys for PNG processing - self.text_keys_to_delete = result.get("text_keys", []) + if isinstance(result, PngProcessor): + self.text_keys_to_delete = result.get("text_keys", []) return self.metadata def wipe(self) -> None: @@ -120,18 +121,16 @@ class ImageHandler(MetadataHandler): raise UnsupportedFormatError(f"Unsupported format: {self.detected_format}") with Image.open(Path(self.filepath)) as img: - self.processed_metadata = processor.delete_metadata( - img, self.tags_to_delete + self.processed_metadata = cast( + dict[str, Any], processor.delete_metadata(img, self.tags_to_delete) ) # For PNG, also get clean PngInfo - if self.detected_format == "png" and hasattr( - processor, "get_clean_pnginfo" - ): + if isinstance(processor, PngProcessor): self.clean_pnginfo = processor.get_clean_pnginfo( img, self.text_keys_to_delete ) - def save(self, output_path: Optional[str | Path] = None) -> None: + def save(self, output_path: str | Path | None = None) -> None: """ Writes the changes to a copy of the original file. diff --git a/src/services/metadata_handler.py b/src/services/metadata_handler.py index dea9b48b..8d0e94f0 100644 --- a/src/services/metadata_handler.py +++ b/src/services/metadata_handler.py @@ -7,7 +7,7 @@ must implement the read, wipe, and save methods. """ from abc import ABC, abstractmethod -from typing import Any, Dict, Optional +from typing import Any class MetadataHandler(ABC): @@ -32,11 +32,11 @@ class MetadataHandler(ABC): filepath: Path to the file to process. """ self.filepath = filepath - self.metadata: Dict[str, Any] = {} - self.processed_metadata: Dict[str, Any] = {} + self.metadata: dict[str, Any] = {} + self.processed_metadata: dict[str, Any] = {} @abstractmethod - def read(self) -> Dict[str, Any]: + def read(self) -> dict[str, Any]: """ Extract metadata from the file. @@ -61,7 +61,7 @@ class MetadataHandler(ABC): pass @abstractmethod - def save(self, output_path: Optional[str] = None) -> None: + def save(self, output_path: str | None = None) -> None: """ Save the processed file with cleaned metadata. diff --git a/src/utils/display.py b/src/utils/display.py index dfb7d657..ccf0fc33 100644 --- a/src/utils/display.py +++ b/src/utils/display.py @@ -5,7 +5,7 @@ This module provides functions for displaying metadata and batch processing results in beautifully formatted tables and panels using the Rich library. """ -from typing import Any, Dict +from typing import Any from rich import box from rich.console import Console @@ -18,7 +18,7 @@ from src.utils.formatter import clean_value console = Console() -def print_metadata_table(metadata: Dict[str, Any]): +def print_metadata_table(metadata: dict[str, Any]): """ Display metadata in a formatted table organized by logical groups. diff --git a/src/utils/get_target_files.py b/src/utils/get_target_files.py index 89a3d9b8..43ac4132 100644 --- a/src/utils/get_target_files.py +++ b/src/utils/get_target_files.py @@ -5,8 +5,8 @@ This module provides functions to find and yield files for processing, supporting recursive directory traversal with extension filtering. """ +from collections.abc import Generator from pathlib import Path -from typing import Generator def get_target_files(input_path_str: Path, ext: str) -> Generator[Path, None, None]: diff --git a/typings/piexif/__init__.pyi b/typings/piexif/__init__.pyi new file mode 100644 index 00000000..580fd2c3 --- /dev/null +++ b/typings/piexif/__init__.pyi @@ -0,0 +1,15 @@ +""" +This type stub file was generated by pyright. +""" + +from ._dump import dump as dump +from ._exceptions import * +from ._exif import * +from ._insert import insert as insert +from ._load import load as load +from ._remove import remove as remove +from ._transplant import transplant as transplant + +__all__ = ["remove", "load", "dump", "transplant", "insert", "VERSION"] + +VERSION: str diff --git a/typings/piexif/_common.pyi b/typings/piexif/_common.pyi new file mode 100644 index 00000000..d44b14ea --- /dev/null +++ b/typings/piexif/_common.pyi @@ -0,0 +1,24 @@ +""" +This type stub file was generated by pyright. +""" + +def split_into_segments(data): # -> list[bytes]: + """Slices JPEG meta data into a list from JPEG binary data. + """ + ... + +def read_exif_from_file(filename): # -> bytes | None: + """Slices JPEG meta data into a list from JPEG binary data. + """ + ... + +def get_exif_seg(segments): # -> None: + """Returns Exif from JPEG meta data list + """ + ... + +def merge_segments(segments, exif=...): # -> bytes: + """Merges Exif with APP0 and APP1 manipulations. + """ + ... + diff --git a/typings/piexif/_dump.pyi b/typings/piexif/_dump.pyi new file mode 100644 index 00000000..43701355 --- /dev/null +++ b/typings/piexif/_dump.pyi @@ -0,0 +1,20 @@ +""" +This type stub file was generated by pyright. +""" + +from ._common import * +from ._exif import * + +TIFF_HEADER_LENGTH = ... +def dump(exif_dict_original): # -> bytes: + """ + py:function:: piexif.load(data) + + Return exif as bytes. + + :param dict exif: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes}) + :return: Exif + :rtype: bytes + """ + ... + diff --git a/typings/piexif/_exceptions.pyi b/typings/piexif/_exceptions.pyi new file mode 100644 index 00000000..7f1d5b52 --- /dev/null +++ b/typings/piexif/_exceptions.pyi @@ -0,0 +1,8 @@ +""" +This type stub file was generated by pyright. +""" + +class InvalidImageDataError(ValueError): + ... + + diff --git a/typings/piexif/_exif.pyi b/typings/piexif/_exif.pyi new file mode 100644 index 00000000..6612462d --- /dev/null +++ b/typings/piexif/_exif.pyi @@ -0,0 +1,341 @@ +""" +Type stubs for piexif EXIF tag definitions. +""" + +from typing import Final + +class TYPES: + """EXIF data type identifiers.""" + + Byte: Final[int] = ... + Ascii: Final[int] = ... + Short: Final[int] = ... + Long: Final[int] = ... + Rational: Final[int] = ... + SByte: Final[int] = ... + Undefined: Final[int] = ... + SShort: Final[int] = ... + SLong: Final[int] = ... + SRational: Final[int] = ... + Float: Final[int] = ... + DFloat: Final[int] = ... + +TAGS: dict[str, dict[int, dict[str, str | int]]] + +class ImageIFD: + """Exif tag number reference - 0th IFD""" + + ProcessingSoftware: Final[int] = ... + NewSubfileType: Final[int] = ... + SubfileType: Final[int] = ... + ImageWidth: Final[int] = ... + ImageLength: Final[int] = ... + BitsPerSample: Final[int] = ... + Compression: Final[int] = ... + PhotometricInterpretation: Final[int] = ... + Threshholding: Final[int] = ... + CellWidth: Final[int] = ... + CellLength: Final[int] = ... + FillOrder: Final[int] = ... + DocumentName: Final[int] = ... + ImageDescription: Final[int] = ... + Make: Final[int] = ... + Model: Final[int] = ... + StripOffsets: Final[int] = ... + Orientation: Final[int] = ... + SamplesPerPixel: Final[int] = ... + RowsPerStrip: Final[int] = ... + StripByteCounts: Final[int] = ... + XResolution: Final[int] = ... + YResolution: Final[int] = ... + PlanarConfiguration: Final[int] = ... + GrayResponseUnit: Final[int] = ... + GrayResponseCurve: Final[int] = ... + T4Options: Final[int] = ... + T6Options: Final[int] = ... + ResolutionUnit: Final[int] = ... + TransferFunction: Final[int] = ... + Software: Final[int] = ... + DateTime: Final[int] = ... + Artist: Final[int] = ... + HostComputer: Final[int] = ... + Predictor: Final[int] = ... + WhitePoint: Final[int] = ... + PrimaryChromaticities: Final[int] = ... + ColorMap: Final[int] = ... + HalftoneHints: Final[int] = ... + TileWidth: Final[int] = ... + TileLength: Final[int] = ... + TileOffsets: Final[int] = ... + TileByteCounts: Final[int] = ... + SubIFDs: Final[int] = ... + InkSet: Final[int] = ... + InkNames: Final[int] = ... + NumberOfInks: Final[int] = ... + DotRange: Final[int] = ... + TargetPrinter: Final[int] = ... + ExtraSamples: Final[int] = ... + SampleFormat: Final[int] = ... + SMinSampleValue: Final[int] = ... + SMaxSampleValue: Final[int] = ... + TransferRange: Final[int] = ... + ClipPath: Final[int] = ... + XClipPathUnits: Final[int] = ... + YClipPathUnits: Final[int] = ... + Indexed: Final[int] = ... + JPEGTables: Final[int] = ... + OPIProxy: Final[int] = ... + JPEGProc: Final[int] = ... + JPEGInterchangeFormat: Final[int] = ... + JPEGInterchangeFormatLength: Final[int] = ... + JPEGRestartInterval: Final[int] = ... + JPEGLosslessPredictors: Final[int] = ... + JPEGPointTransforms: Final[int] = ... + JPEGQTables: Final[int] = ... + JPEGDCTables: Final[int] = ... + JPEGACTables: Final[int] = ... + YCbCrCoefficients: Final[int] = ... + YCbCrSubSampling: Final[int] = ... + YCbCrPositioning: Final[int] = ... + ReferenceBlackWhite: Final[int] = ... + XMLPacket: Final[int] = ... + Rating: Final[int] = ... + RatingPercent: Final[int] = ... + ImageID: Final[int] = ... + CFARepeatPatternDim: Final[int] = ... + CFAPattern: Final[int] = ... + BatteryLevel: Final[int] = ... + Copyright: Final[int] = ... + ExposureTime: Final[int] = ... + ImageResources: Final[int] = ... + ExifTag: Final[int] = ... + InterColorProfile: Final[int] = ... + GPSTag: Final[int] = ... + Interlace: Final[int] = ... + TimeZoneOffset: Final[int] = ... + SelfTimerMode: Final[int] = ... + FlashEnergy: Final[int] = ... + SpatialFrequencyResponse: Final[int] = ... + Noise: Final[int] = ... + FocalPlaneXResolution: Final[int] = ... + FocalPlaneYResolution: Final[int] = ... + FocalPlaneResolutionUnit: Final[int] = ... + ImageNumber: Final[int] = ... + SecurityClassification: Final[int] = ... + ImageHistory: Final[int] = ... + ExposureIndex: Final[int] = ... + TIFFEPStandardID: Final[int] = ... + SensingMethod: Final[int] = ... + XPTitle: Final[int] = ... + XPComment: Final[int] = ... + XPAuthor: Final[int] = ... + XPKeywords: Final[int] = ... + XPSubject: Final[int] = ... + PrintImageMatching: Final[int] = ... + DNGVersion: Final[int] = ... + DNGBackwardVersion: Final[int] = ... + UniqueCameraModel: Final[int] = ... + LocalizedCameraModel: Final[int] = ... + CFAPlaneColor: Final[int] = ... + CFALayout: Final[int] = ... + LinearizationTable: Final[int] = ... + BlackLevelRepeatDim: Final[int] = ... + BlackLevel: Final[int] = ... + BlackLevelDeltaH: Final[int] = ... + BlackLevelDeltaV: Final[int] = ... + WhiteLevel: Final[int] = ... + DefaultScale: Final[int] = ... + DefaultCropOrigin: Final[int] = ... + DefaultCropSize: Final[int] = ... + ColorMatrix1: Final[int] = ... + ColorMatrix2: Final[int] = ... + CameraCalibration1: Final[int] = ... + CameraCalibration2: Final[int] = ... + ReductionMatrix1: Final[int] = ... + ReductionMatrix2: Final[int] = ... + AnalogBalance: Final[int] = ... + AsShotNeutral: Final[int] = ... + AsShotWhiteXY: Final[int] = ... + BaselineExposure: Final[int] = ... + BaselineNoise: Final[int] = ... + BaselineSharpness: Final[int] = ... + BayerGreenSplit: Final[int] = ... + LinearResponseLimit: Final[int] = ... + CameraSerialNumber: Final[int] = ... + LensInfo: Final[int] = ... + ChromaBlurRadius: Final[int] = ... + AntiAliasStrength: Final[int] = ... + ShadowScale: Final[int] = ... + DNGPrivateData: Final[int] = ... + MakerNoteSafety: Final[int] = ... + CalibrationIlluminant1: Final[int] = ... + CalibrationIlluminant2: Final[int] = ... + BestQualityScale: Final[int] = ... + RawDataUniqueID: Final[int] = ... + OriginalRawFileName: Final[int] = ... + OriginalRawFileData: Final[int] = ... + ActiveArea: Final[int] = ... + MaskedAreas: Final[int] = ... + AsShotICCProfile: Final[int] = ... + AsShotPreProfileMatrix: Final[int] = ... + CurrentICCProfile: Final[int] = ... + CurrentPreProfileMatrix: Final[int] = ... + ColorimetricReference: Final[int] = ... + CameraCalibrationSignature: Final[int] = ... + ProfileCalibrationSignature: Final[int] = ... + AsShotProfileName: Final[int] = ... + NoiseReductionApplied: Final[int] = ... + ProfileName: Final[int] = ... + ProfileHueSatMapDims: Final[int] = ... + ProfileHueSatMapData1: Final[int] = ... + ProfileHueSatMapData2: Final[int] = ... + ProfileToneCurve: Final[int] = ... + ProfileEmbedPolicy: Final[int] = ... + ProfileCopyright: Final[int] = ... + ForwardMatrix1: Final[int] = ... + ForwardMatrix2: Final[int] = ... + PreviewApplicationName: Final[int] = ... + PreviewApplicationVersion: Final[int] = ... + PreviewSettingsName: Final[int] = ... + PreviewSettingsDigest: Final[int] = ... + PreviewColorSpace: Final[int] = ... + PreviewDateTime: Final[int] = ... + RawImageDigest: Final[int] = ... + OriginalRawFileDigest: Final[int] = ... + SubTileBlockSize: Final[int] = ... + RowInterleaveFactor: Final[int] = ... + ProfileLookTableDims: Final[int] = ... + ProfileLookTableData: Final[int] = ... + OpcodeList1: Final[int] = ... + OpcodeList2: Final[int] = ... + OpcodeList3: Final[int] = ... + NoiseProfile: Final[int] = ... + ZZZTestSlong1: Final[int] = ... + ZZZTestSlong2: Final[int] = ... + ZZZTestSByte: Final[int] = ... + ZZZTestSShort: Final[int] = ... + ZZZTestDFloat: Final[int] = ... + +class ExifIFD: + """Exif tag number reference - Exif IFD""" + + ExposureTime: Final[int] = ... + FNumber: Final[int] = ... + ExposureProgram: Final[int] = ... + SpectralSensitivity: Final[int] = ... + ISOSpeedRatings: Final[int] = ... + OECF: Final[int] = ... + SensitivityType: Final[int] = ... + StandardOutputSensitivity: Final[int] = ... + RecommendedExposureIndex: Final[int] = ... + ISOSpeed: Final[int] = ... + ISOSpeedLatitudeyyy: Final[int] = ... + ISOSpeedLatitudezzz: Final[int] = ... + ExifVersion: Final[int] = ... + DateTimeOriginal: Final[int] = ... + DateTimeDigitized: Final[int] = ... + OffsetTime: Final[int] = ... + OffsetTimeOriginal: Final[int] = ... + OffsetTimeDigitized: Final[int] = ... + ComponentsConfiguration: Final[int] = ... + CompressedBitsPerPixel: Final[int] = ... + ShutterSpeedValue: Final[int] = ... + ApertureValue: Final[int] = ... + BrightnessValue: Final[int] = ... + ExposureBiasValue: Final[int] = ... + MaxApertureValue: Final[int] = ... + SubjectDistance: Final[int] = ... + MeteringMode: Final[int] = ... + LightSource: Final[int] = ... + Flash: Final[int] = ... + FocalLength: Final[int] = ... + Temperature: Final[int] = ... + Humidity: Final[int] = ... + Pressure: Final[int] = ... + WaterDepth: Final[int] = ... + Acceleration: Final[int] = ... + CameraElevationAngle: Final[int] = ... + SubjectArea: Final[int] = ... + MakerNote: Final[int] = ... + UserComment: Final[int] = ... + SubSecTime: Final[int] = ... + SubSecTimeOriginal: Final[int] = ... + SubSecTimeDigitized: Final[int] = ... + FlashpixVersion: Final[int] = ... + ColorSpace: Final[int] = ... + PixelXDimension: Final[int] = ... + PixelYDimension: Final[int] = ... + RelatedSoundFile: Final[int] = ... + InteroperabilityTag: Final[int] = ... + FlashEnergy: Final[int] = ... + SpatialFrequencyResponse: Final[int] = ... + FocalPlaneXResolution: Final[int] = ... + FocalPlaneYResolution: Final[int] = ... + FocalPlaneResolutionUnit: Final[int] = ... + SubjectLocation: Final[int] = ... + ExposureIndex: Final[int] = ... + SensingMethod: Final[int] = ... + FileSource: Final[int] = ... + SceneType: Final[int] = ... + CFAPattern: Final[int] = ... + CustomRendered: Final[int] = ... + ExposureMode: Final[int] = ... + WhiteBalance: Final[int] = ... + DigitalZoomRatio: Final[int] = ... + FocalLengthIn35mmFilm: Final[int] = ... + SceneCaptureType: Final[int] = ... + GainControl: Final[int] = ... + Contrast: Final[int] = ... + Saturation: Final[int] = ... + Sharpness: Final[int] = ... + DeviceSettingDescription: Final[int] = ... + SubjectDistanceRange: Final[int] = ... + ImageUniqueID: Final[int] = ... + CameraOwnerName: Final[int] = ... + BodySerialNumber: Final[int] = ... + LensSpecification: Final[int] = ... + LensMake: Final[int] = ... + LensModel: Final[int] = ... + LensSerialNumber: Final[int] = ... + Gamma: Final[int] = ... + +class GPSIFD: + """Exif tag number reference - GPS IFD""" + + GPSVersionID: Final[int] = ... + GPSLatitudeRef: Final[int] = ... + GPSLatitude: Final[int] = ... + GPSLongitudeRef: Final[int] = ... + GPSLongitude: Final[int] = ... + GPSAltitudeRef: Final[int] = ... + GPSAltitude: Final[int] = ... + GPSTimeStamp: Final[int] = ... + GPSSatellites: Final[int] = ... + GPSStatus: Final[int] = ... + GPSMeasureMode: Final[int] = ... + GPSDOP: Final[int] = ... + GPSSpeedRef: Final[int] = ... + GPSSpeed: Final[int] = ... + GPSTrackRef: Final[int] = ... + GPSTrack: Final[int] = ... + GPSImgDirectionRef: Final[int] = ... + GPSImgDirection: Final[int] = ... + GPSMapDatum: Final[int] = ... + GPSDestLatitudeRef: Final[int] = ... + GPSDestLatitude: Final[int] = ... + GPSDestLongitudeRef: Final[int] = ... + GPSDestLongitude: Final[int] = ... + GPSDestBearingRef: Final[int] = ... + GPSDestBearing: Final[int] = ... + GPSDestDistanceRef: Final[int] = ... + GPSDestDistance: Final[int] = ... + GPSProcessingMethod: Final[int] = ... + GPSAreaInformation: Final[int] = ... + GPSDateStamp: Final[int] = ... + GPSDifferential: Final[int] = ... + GPSHPositioningError: Final[int] = ... + +class InteropIFD: + """Exif tag number reference - Interoperability IFD""" + + InteroperabilityIndex: Final[int] = ... diff --git a/typings/piexif/_insert.pyi b/typings/piexif/_insert.pyi new file mode 100644 index 00000000..64c35198 --- /dev/null +++ b/typings/piexif/_insert.pyi @@ -0,0 +1,17 @@ +""" +This type stub file was generated by pyright. +""" + +from ._common import * + +def insert(exif, image, new_file=...): # -> None: + """ + py:function:: piexif.insert(exif_bytes, filename) + + Insert exif into JPEG. + + :param bytes exif_bytes: Exif as bytes + :param str filename: JPEG + """ + ... + diff --git a/typings/piexif/_load.pyi b/typings/piexif/_load.pyi new file mode 100644 index 00000000..85138c25 --- /dev/null +++ b/typings/piexif/_load.pyi @@ -0,0 +1,32 @@ +""" +This type stub file was generated by pyright. +""" + +from ._common import * +from ._exif import * + +LITTLE_ENDIAN = ... +def load(input_data, key_is_name=...): # -> dict[str, Any]: + """ + py:function:: piexif.load(filename) + + Return exif data as dict. Keys(IFD name), be contained, are "0th", "Exif", "GPS", "Interop", "1st", and "thumbnail". Without "thumbnail", the value is dict(tag name/tag value). "thumbnail" value is JPEG as bytes. + + :param str filename: JPEG or TIFF + :return: Exif data({"0th":dict, "Exif":dict, "GPS":dict, "Interop":dict, "1st":dict, "thumbnail":bytes}) + :rtype: dict + """ + ... + +class _ExifReader: + def __init__(self, data) -> None: + ... + + def get_ifd_dict(self, pointer, ifd_name, read_unknown=...): # -> dict[Any, Any]: + ... + + def convert_value(self, val): # -> Any | tuple[Any, Any] | bytes | tuple[Any, ...] | tuple[tuple[Any, Any], ...]: + ... + + + diff --git a/typings/piexif/_remove.pyi b/typings/piexif/_remove.pyi new file mode 100644 index 00000000..6c6da59f --- /dev/null +++ b/typings/piexif/_remove.pyi @@ -0,0 +1,16 @@ +""" +This type stub file was generated by pyright. +""" + +from ._common import * + +def remove(src, new_file=...): # -> None: + """ + py:function:: piexif.remove(filename) + + Remove exif from JPEG. + + :param str filename: JPEG + """ + ... + diff --git a/typings/piexif/_transplant.pyi b/typings/piexif/_transplant.pyi new file mode 100644 index 00000000..90f2bb66 --- /dev/null +++ b/typings/piexif/_transplant.pyi @@ -0,0 +1,17 @@ +""" +This type stub file was generated by pyright. +""" + +from ._common import * + +def transplant(exif_src, image, new_file=...): # -> None: + """ + py:function:: piexif.transplant(filename1, filename2) + + Transplant exif from filename1 to filename2. + + :param str filename1: JPEG + :param str filename2: JPEG + """ + ... + diff --git a/typings/piexif/_webp.pyi b/typings/piexif/_webp.pyi new file mode 100644 index 00000000..373f9aea --- /dev/null +++ b/typings/piexif/_webp.pyi @@ -0,0 +1,28 @@ +""" +This type stub file was generated by pyright. +""" + +def split(data): # -> list[Any]: + ... + +def merge_chunks(chunks): # -> bytes: + ... + +def set_vp8x(chunks): + ... + +def get_file_header(chunks): # -> bytes: + ... + +def get_exif(data): # -> None: + ... + +def insert_exif_into_chunks(chunks, exif_bytes): + ... + +def insert(webp_bytes, exif_bytes): # -> bytes: + ... + +def remove(webp_bytes): # -> bytes: + ... + diff --git a/typings/piexif/helper.pyi b/typings/piexif/helper.pyi new file mode 100644 index 00000000..3579d439 --- /dev/null +++ b/typings/piexif/helper.pyi @@ -0,0 +1,44 @@ +""" +This type stub file was generated by pyright. +""" + +class UserComment: + ASCII = ... + JIS = ... + UNICODE = ... + ENCODINGS = ... + _JIS = ... + _UNICODE = ... + _PREFIX_SIZE = ... + _ASCII_PREFIX = ... + _JIS_PREFIX = ... + _UNICODE_PREFIX = ... + _UNDEFINED_PREFIX = ... + @classmethod + def load(cls, data): + """ + Convert "UserComment" value in exif format to str. + + :param bytes data: "UserComment" value from exif + :return: u"foobar" + :rtype: str(Unicode) + :raises: ValueError if the data does not conform to the EXIF specification, + or the encoding is unsupported. + """ + ... + + @classmethod + def dump(cls, data, encoding=...): + """ + Convert str to appropriate format for "UserComment". + + :param data: Like u"foobar" + :param str encoding: "ascii", "jis", or "unicode" + :return: b"ASCII\x00\x00\x00foobar" + :rtype: bytes + :raises: ValueError if the encoding is unsupported. + """ + ... + + +