IPTCInfo3 2.1.4__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iptcinfo3-2.2.0/.gitignore +21 -0
- iptcinfo3-2.2.0/CHANGELOG.md +88 -0
- iptcinfo3-2.2.0/PKG-INFO +87 -0
- {IPTCInfo3-2.1.4 → iptcinfo3-2.2.0}/README.rst +4 -1
- {IPTCInfo3-2.1.4 → iptcinfo3-2.2.0}/iptcinfo3.py +92 -30
- iptcinfo3-2.2.0/pyproject.toml +80 -0
- IPTCInfo3-2.1.4/IPTCInfo3.egg-info/PKG-INFO +0 -72
- IPTCInfo3-2.1.4/IPTCInfo3.egg-info/SOURCES.txt +0 -8
- IPTCInfo3-2.1.4/IPTCInfo3.egg-info/dependency_links.txt +0 -1
- IPTCInfo3-2.1.4/IPTCInfo3.egg-info/top_level.txt +0 -1
- IPTCInfo3-2.1.4/PKG-INFO +0 -72
- IPTCInfo3-2.1.4/setup.cfg +0 -15
- IPTCInfo3-2.1.4/setup.py +0 -63
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
## 2.2.0 (2025-10-02)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- **Issue #40**: Clarified license statements - now consistently states "Artistic-1.0 OR GPL-1.0-or-later"
|
|
5
|
+
- **Issue #24**: Changed "Marker scan hit start of image data" to INFO level when `force=True` is used
|
|
6
|
+
- **Issue #32**: Fixed charset recognition for ISO 2022 escape sequences (UTF-8 as `\x1b%G`)
|
|
7
|
+
- **Issue #26**: Added validation for float/NaN values in `packedIIMData()` to prevent TypeError
|
|
8
|
+
|
|
9
|
+
### New Features
|
|
10
|
+
- **Issue #35**: Added 'credit line' field support per IPTC Core 1.1 (backward compatible with 'credit')
|
|
11
|
+
- **Issue #42**: Added 'destination' field as alias for 'original transmission reference'
|
|
12
|
+
|
|
13
|
+
### Improvements
|
|
14
|
+
- **Issue #15**: Enhanced IPTC tag collection with better field mappings
|
|
15
|
+
- **Issue #38**: Verified backup file behavior (use `options={'overwrite': True}` to avoid ~ files)
|
|
16
|
+
- Better error handling and logging throughout
|
|
17
|
+
|
|
18
|
+
### Notes
|
|
19
|
+
- **Issue #39, #41**: Ready for PyPI release with all fixes from master branch
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
Updating builds to target 3.9.7
|
|
24
|
+
|
|
25
|
+
2.1: Fixes merged to save modified IPTC info images
|
|
26
|
+
|
|
27
|
+
1.9.5-8: https://bitbucket.org/gthomas/iptcinfo/issue/4/file-permissions-for-changed-files-are-not - copy original file's permission bits on save/saveAs
|
|
28
|
+
|
|
29
|
+
1.9.5-7: https://bitbucket.org/gthomas/iptcinfo/issue/3/images-w-o-iptc-data-should-not-log-errors - have silencable parse errors.
|
|
30
|
+
|
|
31
|
+
1.9.5-6: to have a nice new upload (seems easy_install grabs an old version).
|
|
32
|
+
|
|
33
|
+
1.9.5-5: fix some issues with "super"
|
|
34
|
+
|
|
35
|
+
1.9.5-3: use logging module.
|
|
36
|
+
|
|
37
|
+
1.9.5-2: Emil Stenström pinpointed some bugs/misleading (un)comments
|
|
38
|
+
Also a new (mis)feature is implemented: if you don't specify inp_charset
|
|
39
|
+
(and the image misses such information, too) than no conversion is made
|
|
40
|
+
to unicode, everything stays bytestring!
|
|
41
|
+
This way you don't need to deal with charsets, BUT it is your risk to make
|
|
42
|
+
the modifications with the SAME charset as it is in the image!
|
|
43
|
+
|
|
44
|
+
1.9.5-1: getting in sync with the Perl version 1.9.5
|
|
45
|
+
|
|
46
|
+
1.9.2-rc8:
|
|
47
|
+
charset recognition loosened (failed with some image out of
|
|
48
|
+
Adobe Lightroom).
|
|
49
|
+
|
|
50
|
+
1.9.2-rc7: NOT READY
|
|
51
|
+
IPTCInfo now accepts 'inp_charset' keyword for setting input charset.
|
|
52
|
+
|
|
53
|
+
1.9.2-rc6: just PyLint-ed out some errors.
|
|
54
|
+
|
|
55
|
+
1.9.2-rc5: Amos Latteier sent me a patch which releases the requirement of the
|
|
56
|
+
file objects to be file objects (he uses this on jpeg files stored in
|
|
57
|
+
databases as strings).
|
|
58
|
+
It modifies the module in order to look for a read method on the file
|
|
59
|
+
object. If one exists it assumes the argument is a file object, otherwise it
|
|
60
|
+
assumes it's a filename.
|
|
61
|
+
|
|
62
|
+
1.9.2-rc4: on Windows systems, tmpfile may not work correctly - now I use
|
|
63
|
+
cStringIO on file save (to save the file without truncating it on Exception).
|
|
64
|
+
|
|
65
|
+
1.9.2-rc3: some little bug fixes, some safety enhancements (now iptcinfo.py
|
|
66
|
+
will overwrite the original image file (info.save()) only if everything goes
|
|
67
|
+
fine (so if an exception is thrown at writing, it won't cut your original
|
|
68
|
+
file).
|
|
69
|
+
|
|
70
|
+
This is a pre-release version: needs some testing, and has an unfound bug
|
|
71
|
+
(yet): some pictures can be enhanced with iptc data, and iptcinfo.py is able
|
|
72
|
+
to read them, but some other iptc data readers will spit on it.
|
|
73
|
+
|
|
74
|
+
1.9.1: a first release with some little encoding support
|
|
75
|
+
|
|
76
|
+
The class IPTCInfo now has an inp_charset and an out_charset attribute - the
|
|
77
|
+
first is the read image's charset (defaults to the system default charset),
|
|
78
|
+
the second is the charset the writer will use (defaults to inp_charset).
|
|
79
|
+
|
|
80
|
+
Reader will find the charset included in IPTC data (if any, defaults to the
|
|
81
|
+
system's default charset), and use it to read to unicode strings. Writer will
|
|
82
|
+
write using IPTCinfo.out_charset (if it is not set, will not write charset
|
|
83
|
+
IPTC record).
|
|
84
|
+
|
|
85
|
+
With this, it is possible to read and write i18n strings correctly.
|
|
86
|
+
|
|
87
|
+
I haven't tested this functionality thoroughly, and that little test was only
|
|
88
|
+
on my WinXP box only, with the only other IPTC reader: IrfanView.
|
iptcinfo3-2.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: IPTCInfo3
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Extract and modify IPTC metadata from JPEG images
|
|
5
|
+
Project-URL: Homepage, https://github.com/jamesacampbell/iptcinfo3
|
|
6
|
+
Project-URL: Repository, https://github.com/jamesacampbell/iptcinfo3
|
|
7
|
+
Project-URL: Issues, https://github.com/jamesacampbell/iptcinfo3/issues
|
|
8
|
+
Author-email: Tamas Gulacsi <gthomas@fw.hu>
|
|
9
|
+
Maintainer-email: James Campbell <jc@normail.co>
|
|
10
|
+
License: Artistic-1.0 OR GPL-1.0-or-later
|
|
11
|
+
Keywords: exif,iptc,jpeg,metadata,photography
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Artistic License
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-watch>=4.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/x-rst
|
|
32
|
+
|
|
33
|
+
``IPTCINFO 3``
|
|
34
|
+
==============
|
|
35
|
+
|
|
36
|
+
`Build Status <https://api.travis-ci.org/crccheck/iptcinfo3.png>`_
|
|
37
|
+
|
|
38
|
+
Like IPTCInfo but finally compatible for Python 3
|
|
39
|
+
-------------------------------------------------
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Ported from Josh Carter's Perl IPTCInfo-1.9.pm by Tamas Gulacsi
|
|
43
|
+
|
|
44
|
+
Ever wish you add information to your photos like a caption, the place
|
|
45
|
+
you took it, the date, and perhaps even keywords and categories? You
|
|
46
|
+
already can. The International Press Telecommunications Council (IPTC)
|
|
47
|
+
defines a format for exchanging meta-information in news content, and
|
|
48
|
+
that includes photographs. You can embed all kinds of information in
|
|
49
|
+
your images. The trick is putting it to use.
|
|
50
|
+
|
|
51
|
+
That's where this IPTCInfo Python module comes into play. You can embed
|
|
52
|
+
information using many programs, including Adobe Photoshop, and
|
|
53
|
+
IPTCInfo will let your web server -- and other automated server
|
|
54
|
+
programs -- pull it back out. You can use the information directly in
|
|
55
|
+
Python programs, export it to XML, or even export SQL statements ready
|
|
56
|
+
to be fed into a database.
|
|
57
|
+
|
|
58
|
+
Usage
|
|
59
|
+
-----
|
|
60
|
+
|
|
61
|
+
``from iptcinfo3 import IPTCInfo``
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
Create new info object
|
|
65
|
+
``info = IPTCInfo('doge.jpg')``
|
|
66
|
+
|
|
67
|
+
Print list of keywords, supplemental categories, contacts
|
|
68
|
+
``print(info['keywords'])``
|
|
69
|
+
``print(info['supplementalCategories'])``
|
|
70
|
+
``print(info['contacts'])``
|
|
71
|
+
|
|
72
|
+
Get specific attributes...
|
|
73
|
+
``caption = info['caption/abstract']``
|
|
74
|
+
|
|
75
|
+
Create object for file that may not have IPTC data
|
|
76
|
+
``info = IPTCInfo('such_iptc.jpg', force=True)``
|
|
77
|
+
|
|
78
|
+
Add/change an attribute
|
|
79
|
+
``info['caption/abstract'] = 'Witty caption here'``
|
|
80
|
+
``info['supplemental category'] = ['portrait']``
|
|
81
|
+
|
|
82
|
+
Lists for keywords, so you can just append!
|
|
83
|
+
``info['keywords']).append('cool')``
|
|
84
|
+
|
|
85
|
+
Save new info to file
|
|
86
|
+
``info.save()``
|
|
87
|
+
``info.save_as('very_meta.jpg')``
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
``IPTCINFO 3``
|
|
2
2
|
==============
|
|
3
3
|
|
|
4
|
-
`Build Status <https://travis-ci.org/crccheck/iptcinfo3.
|
|
4
|
+
`Build Status <https://api.travis-ci.org/crccheck/iptcinfo3.png>`_
|
|
5
5
|
|
|
6
6
|
Like IPTCInfo but finally compatible for Python 3
|
|
7
7
|
-------------------------------------------------
|
|
@@ -47,6 +47,9 @@ Add/change an attribute
|
|
|
47
47
|
``info['caption/abstract'] = 'Witty caption here'``
|
|
48
48
|
``info['supplemental category'] = ['portrait']``
|
|
49
49
|
|
|
50
|
+
Lists for keywords, so you can just append!
|
|
51
|
+
``info['keywords']).append('cool')``
|
|
52
|
+
|
|
50
53
|
Save new info to file
|
|
51
54
|
``info.save()``
|
|
52
55
|
``info.save_as('very_meta.jpg')``
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
# All rights reserved.
|
|
12
12
|
#
|
|
13
13
|
# This program is free software; you can redistribute it and/or modify
|
|
14
|
-
# it under the
|
|
14
|
+
# it under the terms of the Artistic License or the GNU General Public
|
|
15
|
+
# License (GPL). You may choose either license.
|
|
15
16
|
#
|
|
16
17
|
# VERSION = '1.9';
|
|
17
18
|
"""
|
|
@@ -24,8 +25,9 @@ import shutil
|
|
|
24
25
|
import sys
|
|
25
26
|
import tempfile
|
|
26
27
|
from struct import pack, unpack
|
|
28
|
+
import json
|
|
27
29
|
|
|
28
|
-
__version__ = '2.
|
|
30
|
+
__version__ = '2.2.0'
|
|
29
31
|
__author__ = 'Gulácsi, Tamás'
|
|
30
32
|
__updated_by__ = 'Campbell, James'
|
|
31
33
|
|
|
@@ -177,7 +179,7 @@ def jpeg_get_variable_length(fh):
|
|
|
177
179
|
|
|
178
180
|
# Length includes itself, so must be at least 2
|
|
179
181
|
if length < 2:
|
|
180
|
-
logger.
|
|
182
|
+
logger.warning("jpeg_get_variable_length: erroneous JPEG marker length")
|
|
181
183
|
return 0
|
|
182
184
|
return length - 2
|
|
183
185
|
|
|
@@ -192,7 +194,7 @@ def jpeg_next_marker(fh):
|
|
|
192
194
|
try:
|
|
193
195
|
byte = read_exactly(fh, 1)
|
|
194
196
|
while ord3(byte) != 0xff:
|
|
195
|
-
# logger.
|
|
197
|
+
# logger.warning("jpeg_next_marker: bogus stuff in Jpeg file at: ')
|
|
196
198
|
byte = read_exactly(fh, 1)
|
|
197
199
|
|
|
198
200
|
# Now skip any extra 0xffs, which are valid padding.
|
|
@@ -360,7 +362,7 @@ def jpeg_debug_scan(filename): # pragma: no cover
|
|
|
360
362
|
break
|
|
361
363
|
|
|
362
364
|
if ord3(marker) == 0:
|
|
363
|
-
logger.
|
|
365
|
+
logger.warning("Marker scan failed")
|
|
364
366
|
break
|
|
365
367
|
|
|
366
368
|
elif ord3(marker) == 0xd9:
|
|
@@ -368,7 +370,7 @@ def jpeg_debug_scan(filename): # pragma: no cover
|
|
|
368
370
|
break
|
|
369
371
|
|
|
370
372
|
if not jpeg_skip_variable(fh):
|
|
371
|
-
logger.
|
|
373
|
+
logger.warning("jpeg_skip_variable failed")
|
|
372
374
|
return None
|
|
373
375
|
|
|
374
376
|
|
|
@@ -480,7 +482,7 @@ c_datasets = {
|
|
|
480
482
|
101: 'country/primary location name',
|
|
481
483
|
103: 'original transmission reference',
|
|
482
484
|
105: 'headline',
|
|
483
|
-
110: 'credit',
|
|
485
|
+
110: 'credit line', # Updated from 'credit' to 'credit line' per IPTC Core 1.1
|
|
484
486
|
115: 'source',
|
|
485
487
|
116: 'copyright notice',
|
|
486
488
|
118: 'contact',
|
|
@@ -536,6 +538,12 @@ class IPTCData(dict):
|
|
|
536
538
|
return key
|
|
537
539
|
elif isinstance(key, str) and key.lower() in c_datasets_r:
|
|
538
540
|
return c_datasets_r[key.lower()]
|
|
541
|
+
# Backward compatibility: 'credit' is now 'credit line' per IPTC Core 1.1
|
|
542
|
+
elif isinstance(key, str) and key.lower() == 'credit':
|
|
543
|
+
return 110
|
|
544
|
+
# Alias for compatibility with gThumb/exiftool
|
|
545
|
+
elif isinstance(key, str) and key.lower() == 'destination':
|
|
546
|
+
return 103 # Maps to 'original transmission reference'
|
|
539
547
|
elif key.startswith(cls.c_cust_pre) and key[len(cls.c_cust_pre):].isdigit():
|
|
540
548
|
# example: nonstandard_69 -> 69
|
|
541
549
|
return int(key[len(cls.c_cust_pre):])
|
|
@@ -553,6 +561,13 @@ class IPTCData(dict):
|
|
|
553
561
|
else:
|
|
554
562
|
raise KeyError("Key %s is not in %s!" % (key, list(c_datasets.keys())))
|
|
555
563
|
|
|
564
|
+
def __contains__(self, name):
|
|
565
|
+
try:
|
|
566
|
+
key = self._key_as_int(name)
|
|
567
|
+
except KeyError:
|
|
568
|
+
return False
|
|
569
|
+
return super().__contains__(key)
|
|
570
|
+
|
|
556
571
|
def __getitem__(self, name):
|
|
557
572
|
return self.get(self._key_as_int(name), None)
|
|
558
573
|
|
|
@@ -598,6 +613,7 @@ class IPTCInfo:
|
|
|
598
613
|
'contact': [],
|
|
599
614
|
})
|
|
600
615
|
self._fobj = fobj
|
|
616
|
+
self._force = force
|
|
601
617
|
if duck_typed(fobj, 'read'): # DELETEME
|
|
602
618
|
self._filename = None
|
|
603
619
|
else:
|
|
@@ -613,7 +629,7 @@ class IPTCInfo:
|
|
|
613
629
|
if datafound:
|
|
614
630
|
self.collectIIMInfo(fh)
|
|
615
631
|
else:
|
|
616
|
-
logger.
|
|
632
|
+
logger.warning('No IPTC data found in %s', fobj)
|
|
617
633
|
|
|
618
634
|
def _filepos(self, fh):
|
|
619
635
|
"""For debugging, return what position in the file we are."""
|
|
@@ -630,7 +646,7 @@ class IPTCInfo:
|
|
|
630
646
|
"""Saves Jpeg with IPTC data to a given file name."""
|
|
631
647
|
with smart_open(self._fobj, 'rb') as fh:
|
|
632
648
|
if not file_is_jpeg(fh):
|
|
633
|
-
logger.error('Source file %s is not a Jpeg.' % self.
|
|
649
|
+
logger.error('Source file %s is not a Jpeg.' % self._fobj)
|
|
634
650
|
return None
|
|
635
651
|
|
|
636
652
|
jpeg_parts = jpeg_collect_file_parts(fh)
|
|
@@ -686,8 +702,10 @@ class IPTCInfo:
|
|
|
686
702
|
os.unlink(tmpfn)
|
|
687
703
|
else:
|
|
688
704
|
tmpfh.close()
|
|
689
|
-
if os.path.exists(newfile):
|
|
690
|
-
|
|
705
|
+
if os.path.exists(newfile) and options is not None and 'overwrite' in options:
|
|
706
|
+
os.unlink(newfile)
|
|
707
|
+
elif os.path.exists(newfile):
|
|
708
|
+
shutil.move(newfile, "{file}~".format(file=newfile))
|
|
691
709
|
shutil.move(tmpfn, newfile)
|
|
692
710
|
return True
|
|
693
711
|
|
|
@@ -699,6 +717,9 @@ class IPTCInfo:
|
|
|
699
717
|
def __len__(self):
|
|
700
718
|
return len(self._data)
|
|
701
719
|
|
|
720
|
+
def __contains__(self, key):
|
|
721
|
+
return key in self._data
|
|
722
|
+
|
|
702
723
|
def __getitem__(self, key):
|
|
703
724
|
return self._data[key]
|
|
704
725
|
|
|
@@ -716,7 +737,7 @@ class IPTCInfo:
|
|
|
716
737
|
logger.info("File is JPEG, proceeding with JpegScan")
|
|
717
738
|
return self.jpegScan(fh)
|
|
718
739
|
else:
|
|
719
|
-
logger.
|
|
740
|
+
logger.warning("File not a JPEG, trying blindScan")
|
|
720
741
|
return self.blindScan(fh)
|
|
721
742
|
|
|
722
743
|
c_marker_err = {0: "Marker scan failed",
|
|
@@ -752,14 +773,18 @@ class IPTCInfo:
|
|
|
752
773
|
err = "jpeg_skip_variable failed"
|
|
753
774
|
if err is not None:
|
|
754
775
|
self.error = err
|
|
755
|
-
|
|
776
|
+
# When force=True, log as INFO instead of WARNING since we expect no IPTC data
|
|
777
|
+
if self._force:
|
|
778
|
+
logger.info(err)
|
|
779
|
+
else:
|
|
780
|
+
logger.warning(err)
|
|
756
781
|
return None
|
|
757
782
|
|
|
758
783
|
# If were's here, we must have found the right marker.
|
|
759
784
|
# Now blindScan through the data.
|
|
760
785
|
return self.blindScan(fh, MAX=jpeg_get_variable_length(fh))
|
|
761
786
|
|
|
762
|
-
def blindScan(self, fh, MAX=
|
|
787
|
+
def blindScan(self, fh, MAX=819200):
|
|
763
788
|
"""Scans blindly to first IIM Record 2 tag in the file. This
|
|
764
789
|
method may or may not work on any arbitrary file type, but it
|
|
765
790
|
doesn't hurt to check. We expect to see this tag within the first
|
|
@@ -767,7 +792,7 @@ class IPTCInfo:
|
|
|
767
792
|
depending on how other programs choose to store IIM.)"""
|
|
768
793
|
|
|
769
794
|
offset = 0
|
|
770
|
-
# keep within first
|
|
795
|
+
# keep within first 819200 bytes
|
|
771
796
|
# NOTE: this may need to change
|
|
772
797
|
logger.debug('blindScan: starting scan, max length %d', MAX)
|
|
773
798
|
|
|
@@ -776,7 +801,7 @@ class IPTCInfo:
|
|
|
776
801
|
try:
|
|
777
802
|
temp = read_exactly(fh, 1)
|
|
778
803
|
except EOFException:
|
|
779
|
-
logger.
|
|
804
|
+
logger.warning("BlindScan: hit EOF while scanning")
|
|
780
805
|
return None
|
|
781
806
|
# look for tag identifier 0x1c
|
|
782
807
|
if ord3(temp) == 0x1c:
|
|
@@ -787,15 +812,32 @@ class IPTCInfo:
|
|
|
787
812
|
# found character set's record!
|
|
788
813
|
try:
|
|
789
814
|
temp = read_exactly(fh, jpeg_get_variable_length(fh))
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
815
|
+
cs = None
|
|
816
|
+
# Check for ISO 2022 escape sequence (starts with ESC 0x1b)
|
|
817
|
+
if len(temp) >= 3 and ord3(temp[0]) == 0x1b:
|
|
818
|
+
# Parse ISO 2022 escape sequences
|
|
819
|
+
# ESC % G = UTF-8
|
|
820
|
+
if temp == b'\x1b%G':
|
|
821
|
+
self.inp_charset = 'utf_8'
|
|
822
|
+
# ESC % / @ = UTF-16 (not commonly used)
|
|
823
|
+
elif temp == b'\x1b%/@':
|
|
824
|
+
self.inp_charset = 'utf_16'
|
|
825
|
+
else:
|
|
826
|
+
logger.debug(
|
|
827
|
+
"BlindScan: unknown ISO 2022 charset escape sequence %r",
|
|
828
|
+
temp)
|
|
829
|
+
else:
|
|
830
|
+
# Try legacy numeric charset encoding
|
|
831
|
+
try:
|
|
832
|
+
cs = unpack('!H', temp)[0]
|
|
833
|
+
if cs in c_charset:
|
|
834
|
+
self.inp_charset = c_charset[cs]
|
|
835
|
+
except Exception:
|
|
836
|
+
logger.debug('BlindScan: could not parse charset from %r', temp)
|
|
837
|
+
|
|
838
|
+
if self.inp_charset:
|
|
839
|
+
logger.info("BlindScan: found character set '%s' at offset %d",
|
|
840
|
+
self.inp_charset, offset)
|
|
799
841
|
except EOFException:
|
|
800
842
|
pass
|
|
801
843
|
|
|
@@ -845,7 +887,7 @@ class IPTCInfo:
|
|
|
845
887
|
try:
|
|
846
888
|
value = str(value, encoding=self.inp_charset, errors='strict')
|
|
847
889
|
except Exception: # TODO better exception
|
|
848
|
-
logger.
|
|
890
|
+
logger.warning('Data "%r" is not in encoding %s!', value, self.inp_charset)
|
|
849
891
|
value = str(value, encoding=self.inp_charset, errors='replace')
|
|
850
892
|
|
|
851
893
|
# try to extract first into _listdata (keywords, categories)
|
|
@@ -889,11 +931,22 @@ class IPTCInfo:
|
|
|
889
931
|
LOGDBG.debug('out=%s', hex_dump(out))
|
|
890
932
|
# Iterate over data sets
|
|
891
933
|
for dataset, value in self._data.items():
|
|
892
|
-
|
|
934
|
+
# Skip None, empty strings, empty lists, and NaN values
|
|
935
|
+
if value is None:
|
|
936
|
+
continue
|
|
937
|
+
# Handle float/int that might be NaN
|
|
938
|
+
if isinstance(value, (float, int)):
|
|
939
|
+
import math
|
|
940
|
+
if isinstance(value, float) and math.isnan(value):
|
|
941
|
+
continue
|
|
942
|
+
# Convert numeric values to strings
|
|
943
|
+
value = str(value)
|
|
944
|
+
# Check length for strings and lists
|
|
945
|
+
if hasattr(value, '__len__') and len(value) == 0:
|
|
893
946
|
continue
|
|
894
947
|
|
|
895
948
|
if not (isinstance(dataset, int) and dataset in c_datasets):
|
|
896
|
-
logger.
|
|
949
|
+
logger.warning("packedIIMData: illegal dataname '%s' (%d)", dataset, dataset)
|
|
897
950
|
continue
|
|
898
951
|
|
|
899
952
|
logger.debug('packedIIMData %02X: %r -> %r', dataset, value, self._enc(value))
|
|
@@ -944,7 +997,16 @@ class IPTCInfo:
|
|
|
944
997
|
|
|
945
998
|
|
|
946
999
|
if __name__ == '__main__': # pragma: no cover
|
|
947
|
-
logging.basicConfig(level=logging.
|
|
1000
|
+
logging.basicConfig(level=logging.ERROR)
|
|
948
1001
|
if len(sys.argv) > 1:
|
|
949
1002
|
info = IPTCInfo(sys.argv[1])
|
|
950
|
-
|
|
1003
|
+
if info.__dict__ != '':
|
|
1004
|
+
for k, v in info.__dict__.items():
|
|
1005
|
+
if k == '_data':
|
|
1006
|
+
print(k)
|
|
1007
|
+
for key, value in v.items():
|
|
1008
|
+
if type(value) == list:
|
|
1009
|
+
print(key, [x.decode() for x in value])
|
|
1010
|
+
[print(x.decode()) for x in value]
|
|
1011
|
+
print(key, value)
|
|
1012
|
+
print(k, v)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "IPTCInfo3"
|
|
7
|
+
version = "2.2.0"
|
|
8
|
+
description = "Extract and modify IPTC metadata from JPEG images"
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
license = { text = "Artistic-1.0 OR GPL-1.0-or-later" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Tamas Gulacsi", email = "gthomas@fw.hu" },
|
|
13
|
+
]
|
|
14
|
+
maintainers = [
|
|
15
|
+
{ name = "James Campbell", email = "jc@normail.co" },
|
|
16
|
+
]
|
|
17
|
+
keywords = ["iptc", "metadata", "jpeg", "exif", "photography"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"License :: OSI Approved :: Artistic License",
|
|
21
|
+
"License :: OSI Approved :: GNU General Public License (GPL)",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Programming Language :: Python",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.8",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
31
|
+
"Topic :: Multimedia :: Graphics",
|
|
32
|
+
"Topic :: Utilities",
|
|
33
|
+
]
|
|
34
|
+
requires-python = ">=3.8"
|
|
35
|
+
dependencies = []
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/jamesacampbell/iptcinfo3"
|
|
39
|
+
Repository = "https://github.com/jamesacampbell/iptcinfo3"
|
|
40
|
+
Issues = "https://github.com/jamesacampbell/iptcinfo3/issues"
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=7.0",
|
|
45
|
+
"pytest-cov>=4.0",
|
|
46
|
+
"pytest-watch>=4.2",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.sdist]
|
|
50
|
+
include = [
|
|
51
|
+
"iptcinfo3.py",
|
|
52
|
+
"README.rst",
|
|
53
|
+
"CHANGELOG.md",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel]
|
|
58
|
+
packages = ["."]
|
|
59
|
+
only-include = ["iptcinfo3.py"]
|
|
60
|
+
|
|
61
|
+
[tool.pytest.ini_options]
|
|
62
|
+
testpaths = ["tests"]
|
|
63
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
64
|
+
|
|
65
|
+
[tool.coverage.run]
|
|
66
|
+
branch = true
|
|
67
|
+
source = ["."]
|
|
68
|
+
|
|
69
|
+
[tool.coverage.report]
|
|
70
|
+
show_missing = true
|
|
71
|
+
skip_covered = true
|
|
72
|
+
|
|
73
|
+
[tool.ruff]
|
|
74
|
+
line-length = 100
|
|
75
|
+
target-version = "py38"
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint]
|
|
78
|
+
select = ["E", "F", "W", "I", "N", "UP"]
|
|
79
|
+
ignore = ["E501"] # Line length handled by formatter
|
|
80
|
+
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.2
|
|
2
|
-
Name: IPTCInfo3
|
|
3
|
-
Version: 2.1.4
|
|
4
|
-
Summary: A great way to get IPTCInfo
|
|
5
|
-
Home-page: https://github.com/jamesacampbell/iptcinfo3
|
|
6
|
-
Author: Tamas Gulacsi
|
|
7
|
-
Author-email: gthomas@fw.hu
|
|
8
|
-
Maintainer: James Campbell
|
|
9
|
-
Maintainer-email: jc@normail.co
|
|
10
|
-
License: http://www.opensource.org/licenses/gpl-license.php
|
|
11
|
-
Download-URL: https://github.com/jamesacampbell/iptcinfo3
|
|
12
|
-
Description: ``IPTCINFO 3``
|
|
13
|
-
==============
|
|
14
|
-
|
|
15
|
-
`Build Status <https://travis-ci.org/crccheck/iptcinfo3.svg?branch=master>`_
|
|
16
|
-
|
|
17
|
-
Like IPTCInfo but finally compatible for Python 3
|
|
18
|
-
-------------------------------------------------
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Ported from Josh Carter's Perl IPTCInfo-1.9.pm by Tamas Gulacsi
|
|
22
|
-
|
|
23
|
-
Ever wish you add information to your photos like a caption, the place
|
|
24
|
-
you took it, the date, and perhaps even keywords and categories? You
|
|
25
|
-
already can. The International Press Telecommunications Council (IPTC)
|
|
26
|
-
defines a format for exchanging meta-information in news content, and
|
|
27
|
-
that includes photographs. You can embed all kinds of information in
|
|
28
|
-
your images. The trick is putting it to use.
|
|
29
|
-
|
|
30
|
-
That's where this IPTCInfo Python module comes into play. You can embed
|
|
31
|
-
information using many programs, including Adobe Photoshop, and
|
|
32
|
-
IPTCInfo will let your web server -- and other automated server
|
|
33
|
-
programs -- pull it back out. You can use the information directly in
|
|
34
|
-
Python programs, export it to XML, or even export SQL statements ready
|
|
35
|
-
to be fed into a database.
|
|
36
|
-
|
|
37
|
-
Usage
|
|
38
|
-
-----
|
|
39
|
-
|
|
40
|
-
``from iptcinfo3 import IPTCInfo``
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Create new info object
|
|
44
|
-
``info = IPTCInfo('doge.jpg')``
|
|
45
|
-
|
|
46
|
-
Print list of keywords, supplemental categories, contacts
|
|
47
|
-
``print(info['keywords'])``
|
|
48
|
-
``print(info['supplementalCategories'])``
|
|
49
|
-
``print(info['contacts'])``
|
|
50
|
-
|
|
51
|
-
Get specific attributes...
|
|
52
|
-
``caption = info['caption/abstract']``
|
|
53
|
-
|
|
54
|
-
Create object for file that may not have IPTC data
|
|
55
|
-
``info = IPTCInfo('such_iptc.jpg', force=True)``
|
|
56
|
-
|
|
57
|
-
Add/change an attribute
|
|
58
|
-
``info['caption/abstract'] = 'Witty caption here'``
|
|
59
|
-
``info['supplemental category'] = ['portrait']``
|
|
60
|
-
|
|
61
|
-
Save new info to file
|
|
62
|
-
``info.save()``
|
|
63
|
-
``info.save_as('very_meta.jpg')``
|
|
64
|
-
|
|
65
|
-
Platform: any
|
|
66
|
-
Classifier: Development Status :: 3 - Alpha
|
|
67
|
-
Classifier: License :: OSI Approved :: Artistic License
|
|
68
|
-
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
|
|
69
|
-
Classifier: Intended Audience :: Developers
|
|
70
|
-
Classifier: Programming Language :: Python
|
|
71
|
-
Classifier: Topic :: Multimedia :: Graphics
|
|
72
|
-
Classifier: Topic :: Utilities
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
iptcinfo3
|
IPTCInfo3-2.1.4/PKG-INFO
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.2
|
|
2
|
-
Name: IPTCInfo3
|
|
3
|
-
Version: 2.1.4
|
|
4
|
-
Summary: A great way to get IPTCInfo
|
|
5
|
-
Home-page: https://github.com/jamesacampbell/iptcinfo3
|
|
6
|
-
Author: Tamas Gulacsi
|
|
7
|
-
Author-email: gthomas@fw.hu
|
|
8
|
-
Maintainer: James Campbell
|
|
9
|
-
Maintainer-email: jc@normail.co
|
|
10
|
-
License: http://www.opensource.org/licenses/gpl-license.php
|
|
11
|
-
Download-URL: https://github.com/jamesacampbell/iptcinfo3
|
|
12
|
-
Description: ``IPTCINFO 3``
|
|
13
|
-
==============
|
|
14
|
-
|
|
15
|
-
`Build Status <https://travis-ci.org/crccheck/iptcinfo3.svg?branch=master>`_
|
|
16
|
-
|
|
17
|
-
Like IPTCInfo but finally compatible for Python 3
|
|
18
|
-
-------------------------------------------------
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Ported from Josh Carter's Perl IPTCInfo-1.9.pm by Tamas Gulacsi
|
|
22
|
-
|
|
23
|
-
Ever wish you add information to your photos like a caption, the place
|
|
24
|
-
you took it, the date, and perhaps even keywords and categories? You
|
|
25
|
-
already can. The International Press Telecommunications Council (IPTC)
|
|
26
|
-
defines a format for exchanging meta-information in news content, and
|
|
27
|
-
that includes photographs. You can embed all kinds of information in
|
|
28
|
-
your images. The trick is putting it to use.
|
|
29
|
-
|
|
30
|
-
That's where this IPTCInfo Python module comes into play. You can embed
|
|
31
|
-
information using many programs, including Adobe Photoshop, and
|
|
32
|
-
IPTCInfo will let your web server -- and other automated server
|
|
33
|
-
programs -- pull it back out. You can use the information directly in
|
|
34
|
-
Python programs, export it to XML, or even export SQL statements ready
|
|
35
|
-
to be fed into a database.
|
|
36
|
-
|
|
37
|
-
Usage
|
|
38
|
-
-----
|
|
39
|
-
|
|
40
|
-
``from iptcinfo3 import IPTCInfo``
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Create new info object
|
|
44
|
-
``info = IPTCInfo('doge.jpg')``
|
|
45
|
-
|
|
46
|
-
Print list of keywords, supplemental categories, contacts
|
|
47
|
-
``print(info['keywords'])``
|
|
48
|
-
``print(info['supplementalCategories'])``
|
|
49
|
-
``print(info['contacts'])``
|
|
50
|
-
|
|
51
|
-
Get specific attributes...
|
|
52
|
-
``caption = info['caption/abstract']``
|
|
53
|
-
|
|
54
|
-
Create object for file that may not have IPTC data
|
|
55
|
-
``info = IPTCInfo('such_iptc.jpg', force=True)``
|
|
56
|
-
|
|
57
|
-
Add/change an attribute
|
|
58
|
-
``info['caption/abstract'] = 'Witty caption here'``
|
|
59
|
-
``info['supplemental category'] = ['portrait']``
|
|
60
|
-
|
|
61
|
-
Save new info to file
|
|
62
|
-
``info.save()``
|
|
63
|
-
``info.save_as('very_meta.jpg')``
|
|
64
|
-
|
|
65
|
-
Platform: any
|
|
66
|
-
Classifier: Development Status :: 3 - Alpha
|
|
67
|
-
Classifier: License :: OSI Approved :: Artistic License
|
|
68
|
-
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
|
|
69
|
-
Classifier: Intended Audience :: Developers
|
|
70
|
-
Classifier: Programming Language :: Python
|
|
71
|
-
Classifier: Topic :: Multimedia :: Graphics
|
|
72
|
-
Classifier: Topic :: Utilities
|
IPTCInfo3-2.1.4/setup.cfg
DELETED
IPTCInfo3-2.1.4/setup.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""SETUP THIS STUFF."""
|
|
2
|
-
import sys
|
|
3
|
-
from distutils.command.sdist import sdist as _sdist
|
|
4
|
-
from os import path
|
|
5
|
-
|
|
6
|
-
from setuptools import setup
|
|
7
|
-
classifiers = """
|
|
8
|
-
Development Status :: 3 - Alpha
|
|
9
|
-
License :: OSI Approved :: Artistic License
|
|
10
|
-
License :: OSI Approved :: GNU General Public License (GPL)
|
|
11
|
-
Intended Audience :: Developers
|
|
12
|
-
Programming Language :: Python
|
|
13
|
-
Topic :: Multimedia :: Graphics
|
|
14
|
-
Topic :: Utilities
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
this_directory = path.abspath(path.dirname(__file__))
|
|
18
|
-
with open(path.join(this_directory, 'README.rst')) as f:
|
|
19
|
-
long_description = f.read()
|
|
20
|
-
|
|
21
|
-
if sys.version_info < (2, 3):
|
|
22
|
-
_setup = setup
|
|
23
|
-
|
|
24
|
-
def setup(**kwargs):
|
|
25
|
-
if "classifiers" in kwargs:
|
|
26
|
-
del kwargs["classifiers"]
|
|
27
|
-
_setup(**kwargs)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class sdist(_sdist, object):
|
|
31
|
-
def run(self):
|
|
32
|
-
import os
|
|
33
|
-
res = _sdist.run(self)
|
|
34
|
-
print((self.get_archive_files()))
|
|
35
|
-
for fn in self.get_archive_files():
|
|
36
|
-
os.system('scp -p %s gtmainbox:/var/www/html/python/' % fn)
|
|
37
|
-
return res
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def openfile(fname):
|
|
41
|
-
import os
|
|
42
|
-
return open(os.path.join(os.path.dirname(__file__), fname))
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
version = next((row.split('=', 1)[-1].strip().strip("'").strip('"')
|
|
46
|
-
for row in open('iptcinfo3.py', 'r')
|
|
47
|
-
if row.startswith('__version__')))
|
|
48
|
-
setup( # cmdclass={'sdist': sdist},
|
|
49
|
-
name='IPTCInfo3',
|
|
50
|
-
version=version,
|
|
51
|
-
url='https://github.com/jamesacampbell/iptcinfo3',
|
|
52
|
-
download_url='https://github.com/jamesacampbell/iptcinfo3',
|
|
53
|
-
author='Tamas Gulacsi',
|
|
54
|
-
author_email='gthomas@fw.hu',
|
|
55
|
-
maintainer='James Campbell',
|
|
56
|
-
maintainer_email='jc@normail.co',
|
|
57
|
-
long_description=long_description,
|
|
58
|
-
license='http://www.opensource.org/licenses/gpl-license.php',
|
|
59
|
-
platforms=['any'],
|
|
60
|
-
description="""A great way to get IPTCInfo""",
|
|
61
|
-
classifiers=[_f for _f in classifiers.split('\n') if _f],
|
|
62
|
-
py_modules=['iptcinfo3'],
|
|
63
|
-
)
|