Package: mediameta
Metadata extractor from image and video filesmediametaprovidesImageMetadataandVideoMetadataclasses which facilitate extracting metadata information from media files.mediametawas written and will be maintained without thrid-party image manupulation libraries or modules as they might be licensed.mediametais distributed under GNU General Public License.Install it from PyPi withpip install mediametaSeehttps://github.com/dandelion-systems/mediautilitiesfor usage samples.Copyright 2022 Dandelion Systems <dandelion.systems at gmail.com>mediametawas inspired and partially based on:exiftoolby Phil Harveyexif-heic-js, copyright (c) 2019 Jim LiuCurrentlyImageMetadataclass supportsJPEGHEICTIFFfile formats. Depending on the content of the metadata fields available in a file it extractsTIFFheaders,EXIFdata andGPSdata.Please note that the current implementation loads entire TIFF files into memory for processing and is therefore not recommended for use with TIFF files as they might get very big. JPEG and HEIC files are handled optimally by loading only the metadata into memory.VideoMetadataclass only supports Apple QuickTime MOV files in this release. It extracts all metadata it finds in the moov/meta atom of the file.Usage summaryThe usage of both classes is straigthforward. Just instaciate them supplying the name of the media file. In case the constructor cannot understand what the file is, it throws anUnsupportedMediaFileexception. For exampleimport mediameta as mm
import os

# Iterate through files in a given directory
for f in os.scandir('./img'):
	# Skip subdirectories and links
	if not f.is_file(follow_symlinks=False):
		continue

	# Try and load the metadata
	try:
		meta_data = mm.ImageMetadata(f.path)
	except mm.UnsupportedMediaFile:
		print(f.path + ' - format is not supported.')
		continue

	# If success show it
	print('Metadata in ' + f.path)
	print(meta_data)mediametamodule declares image metadata keys in three dictionaries_TiffTags_ExifTags_GPSTagsNote: some keys defined by the latest revisions of the EXIF standard, especially the ones used by some equipment and software vendors are not declared. However, such keys will be read and stored asTag 0xXXXX (DDDDD). XXXX and DDDDD stand for hexadecimal and decimal values of the unknown tag respectively.If you wish to obtain individual key values from a file's metadata, you should use the literals from these dictionaries as keys to index the object ofImageMetadata. For instance, theprint()calls in the example above could look like this:print('Metadata in ' + f.path)
	print('Picture taken on ' + meta_data['DateTimeOriginal'])
	print('at the location with GPS coordinates')
	print(meta_data['GPSLatitude'] + meta_data['GPSLatitudeRef'])
	print(meta_data['GPSLongitude'] + meta_data['GPSLongitudeRef'])A dictionary with metadata keys forVideoMetadatais not included as these keys are stored in the MOV files by their literal names. Apple defines a set of such literals in itsdeveloper documentation. You are encouraged to use the keys listed inTables 3-6 and 3-7 of Apple developer documentationto try and retrieve metadata from Quicktime MOV files but the result is not guaranteed. It all depends on the author of the video file. Alternatively, you can iterate throughkeys()orall()to get all the metadata we could collect from a MOV file and then decide which ones you need. For instance, the videos taken with iPhones are likely to have at least these metadata keys:com.apple.quicktime.makecom.apple.quicktime.modelcom.apple.quicktime.softwarecom.apple.quicktime.creationdatecom.apple.quicktime.location.ISO6709Data modelBothImageMetadataandVideoMetadataare subclasses ofMediaMetadatawhich is a dummy class providing declarations of common fields, binary data manipulation methods, and metadata access methods. The latter is documented below. You should never need to instaciate the top level class.__init__(file_name:str, encoding:str = 'utf_8')- the constructor, this is where all metadata is scanned inImageMetadataandVideoMetadata. It requires just the name of the file containing media.encodingis optional and used to decode string values from byte sequences in the metadata.encodingshould be one of Python supportedStandard encodings. In case decoding fails the offending symbols in a string will be replaced with � (U+FFFD).__getitem__(key:str)- retrieves the metadata value for a specifickeyallowing the objects ofMediaMetadataand its descendants to be indexed with[]. If thekeyis not present in the file's headers a None value is returned. If thekeyis present and a single value is stored under it, this value is returned. If thekeyholds mulptiple values like, for instance, in the case of GPS coordinates, they are returned as a list. If the object was interpreted (seeinterpret()below), the interpreted values are returned.Note: For tags that have not been interpreted, rational type values are returned as 'numerator/denominator' strings. For example, in the case ofExposureTimetag you will see something like'1/3003'as its value. This is done to preserve the original metadata and to avoid division by zero as might happen, for instance, inLensSpecificationtag recording an unknown F number in0/0notation.__str__()- casts the object tostrtype returning a string of tab separated metadata key/value pairs found in the media file each followed by a line separator. The format of values follows the logic documented for__getitem__(). Useful to import the data into a spreadsheet. Or if you are creaing a command line tool, the output can be fed toawkorgrepfor further processing.Note: there are non-printable tags which will not show in the output. The tags of UNDEFINED type (== 7) are such as well as some others that typically contain binary data. For instance, MakerNote and UserComment. Tags with typically long outputs will not show either though they might be perfectly printable like XMLPacket and StripOffsets. If you wish to access these, use[]to get them directly from the class instance.all()- a generator yielding tuples of(key, value)found in the media file. The format of values follows the logic documented for__getitem__().keys()- returns alistof all keys found in the media file.file_name()andfile_extension()- return the file name that was supplied to the class constructor and the capitalised extesion respectively. The extesion can be used in further releases/forks to manipulate the metadata which implies knowing the original file type.interpret()- calling this function would attempt at converting the tag's values to their human-readable form. This function attemps to locate a dictionary or a function with exactly the same name as the tag. If a dictionary is found, it tries to map the values of the tag to the ones in the dictionary. If a function is found, the tag's value is passed to it and the result is then stored as an interpreted value.The interpreters (dictionaries and functions) defined in the package are documented below. Should you wish to overrride them, or write an interpreter for another tag, just define it in your code and register withassign_interpreter()prior to callinginterpret().Even if an interpreter for a tag is not available,interpret()will attempt to convert rational values to their decimal form, e.g. 1/4 will be converted to 0.25.revert_interpretation()- reverts the tags back to their original values as they were obtained from the media file.assign_interpreter(tag: str, interpreter)- assigns an interpreter fortag.interpretermust be a dictionary or a function. A dictionary must define a mapping between the tag's values and their human-readable form. Al least, this is the primary goal of interpreters. An interpreter function does the same but with whatever logic the developer thinks is right. It must accept a list of values as an input (even if there is only one value) and return a list as well.Use it even if there is a default interpreter for a tag. The assignment will override it.drop_interpreter(tag: str)- reverts assignment byassign_interpreter()Interpreters referenceDictionaries1..1011..2021..30OrientationExposureProgramMeteringModeLightSourceFlashSensingMethodSceneCaptureTypeSceneTypeCustomRenderedGainControlWhiteBalanceContrastSaturationSharpnessSubjectDistanceRangeFileSourceComponentsResolutionUnitFocalPlaneResolutionUnitPhotometricInterpretationCompressionPlanarConfigurationYCbCrPositioningColorSpaceExposureModePredictorGPSAltitudeRefGPSSpeedRefGPSImgDirectionRefGPSDestBearingRefFunctionsIntepreter FunctionActionExifVersionReturns a version string, e.g.2.3FlashpixVersionReturns a version stringInteroperabilityVersionReturns a version stringExposureTimeReturns a rational in secondsShutterSpeedValueReturns an APEX value, e.g.2.0 EvApertureValueReturns an APEX valueExposureBiasValueReturns an APEX valueMaxApertureValueReturns an APEX valueBrightnessValueReturns an APEX value orUnknownif 0xFFFFFFFF is foundFocalLengthReturns a focal length in mm, e.g.4 mmFocalLengthIn35mmFilmReturns a focal length in mmLensSpecificationReturns a string with min and max focal length and min Fnumber for both, e.g.f min = 18 (f/1.8), f max = 24 (f/1.8)FNumberConverts a rational value of FNumber to a string 'f/__' with a decimal value following the slashGPSLatitudeConverts GPS latitude to dd°mm'ss"N format, e.g.41°4'0.6"GPSLongitudeSame as GPSLatitude but for the longtitudeGPSHPositioningErrorConverts the error to string in meters, e.g.24 mGPSAltitudeConverts the altitude to string in meters, e.g.156 mGPSSpeedReturns the speed as formatted string with float valueGPSImgDirectionReturns the direction as formatted string with float value and degrees sign at the end ('°', '\xB0')GPSDestBearingSame as GPSImgDirection but for the bearingGPSVersionIDFunction referenceThere are a few useful functions that come predefined with the package, should you wish to use them in your code.str_to_rational(a:str)- converts a 'numerator/denominator' string tofloatorintif the the numbers are exact multiplesformat_rational(x:int | float, num_digits:int = 2)- returns a string containing an integer value or a floating point value rounded tonum_digitsdecimal points.GPS_link(lat:str, lat_ref:str, lng:str, lng_ref:str, service:str='google')- returns the maps link for the supplied coordinates. The coordinates must be obtained after callinginterpret(). Supported providers are Google, Yandex, OpenStreetMaps and Microsoft Bing. Samples follow:google_maps = GPS_link('41°4'0.6"', 'N', '29°1'9.46"', 'E')
yandex_maps = GPS_link('41°4'0.6"', 'N', '29°1'9.46"', 'E', 'yandex')
openst_maps = GPS_link('41°4'0.6"', 'N', '29°1'9.46"', 'E', 'osm')
msbing_maps = GPS_link('41°4'0.6"', 'N', '29°1'9.46"', 'E', 'bing')LinkSample resultgoogle_mapshttps://www.google.com/maps/place/41.066833,29.019294yandex_mapshttps://yandex.com/maps/?ll=29.019294,41.066833&pt=29.019294,41.066833&z=17&l=mapopenst_mapshttps://www.openstreetmap.org/?mlat=41.066833&mlon=29.019294#map=17/41.066833/29.019294msbing_mapshttps://www.bing.com/maps?cp=41.066833~long&lvl=17&sp=point.41.066833_29.019294_Photo%20GPS%20location