Bumping for 0.2.0 release, added to mac instructions.
This commit is contained in:
parent
e6032a8b80
commit
ed79e61a35
8 changed files with 78 additions and 59 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
Jul 11, 2013 - v0.2.0 Support for Python 2.7 on windows, OpenJPEG 1.5.1.
|
||||||
|
|
||||||
Jun 27, 2013 - v0.1.10 Can wrap codestreams in custom JP2 jackets. Exposing
|
Jun 27, 2013 - v0.1.10 Can wrap codestreams in custom JP2 jackets. Exposing
|
||||||
parameter to specify multi component transform. Added a raw codestream
|
parameter to specify multi component transform. Added a raw codestream
|
||||||
file.
|
file.
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ copyright = u'2013, John Evans'
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.1'
|
version = '0.1'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.1.10'
|
release = '0.2.0'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ additionally be installed:
|
||||||
* py33-scikit-image and either py33-Pillow or freeimage
|
* py33-scikit-image and either py33-Pillow or freeimage
|
||||||
* py33-matplotlib and py33-Pillow
|
* py33-matplotlib and py33-Pillow
|
||||||
|
|
||||||
|
MacPorts supplies both OpenJPEG 1.5.0 and OpenJPEG 2.0.0. As previously
|
||||||
|
mentioned, the 2.0.0 official release is not supported (although the 2.0+
|
||||||
|
development version via SVN *is* supported).
|
||||||
|
|
||||||
Linux
|
Linux
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ about OpenJPEG, please consult http://www.openjpeg.org.
|
||||||
If you use MacPorts on the mac or if you have a sufficiently recent version of
|
If you use MacPorts on the mac or if you have a sufficiently recent version of
|
||||||
Linux, your package manager should already provide you with at least version
|
Linux, your package manager should already provide you with at least version
|
||||||
1.5.1 of OpenJPEG, which means that glymur can be installed ready to read JPEG
|
1.5.1 of OpenJPEG, which means that glymur can be installed ready to read JPEG
|
||||||
2000 images. If you use windows, I suggest using the 1.5.1 windows installer provided
|
2000 images. If you use windows, I suggest using the 1.5.1 windows installer
|
||||||
to you by the OpenJPEG folks at
|
provided to you by the OpenJPEG folks at
|
||||||
https://code.google.com/p/openjpeg/downloads/list .
|
https://code.google.com/p/openjpeg/downloads/list .
|
||||||
|
|
||||||
Glymur Installation
|
Glymur Installation
|
||||||
|
|
@ -56,5 +56,5 @@ You can run the tests from within python as follows::
|
||||||
>>> glymur.runtests()
|
>>> glymur.runtests()
|
||||||
|
|
||||||
Many tests are currently skipped; in fact most of them are skipped if you
|
Many tests are currently skipped; in fact most of them are skipped if you
|
||||||
are relying on OpenJPEG 1.5.1. But the important thing is whether or not any
|
are relying on OpenJPEG 1.5.1. But the important thing, though, is whether or
|
||||||
tests fail.
|
not any tests fail.
|
||||||
|
|
|
||||||
|
|
@ -38,11 +38,13 @@ _METHOD_DISPLAY = {
|
||||||
ANY_ICC_PROFILE: 'any ICC profile',
|
ANY_ICC_PROFILE: 'any ICC profile',
|
||||||
VENDOR_COLOR_METHOD: 'vendor color method'}
|
VENDOR_COLOR_METHOD: 'vendor color method'}
|
||||||
|
|
||||||
_ = {1: 'accurately represents correct colorspace definition',
|
_APPROX_DISPLAY = {1: 'accurately represents correct colorspace definition',
|
||||||
2: 'approximates correct colorspace definition, exceptional quality',
|
2: 'approximates correct colorspace definition, '
|
||||||
3: 'approximates correct colorspace definition, reasonable quality',
|
+ 'exceptional quality',
|
||||||
4: 'approximates correct colorspace definition, poor quality'}
|
3: 'approximates correct colorspace definition, '
|
||||||
_approximation_display = _
|
+ 'reasonable quality',
|
||||||
|
4: 'approximates correct colorspace definition, '
|
||||||
|
+ 'poor quality'}
|
||||||
|
|
||||||
|
|
||||||
class Jp2kBox(object):
|
class Jp2kBox(object):
|
||||||
|
|
@ -66,12 +68,15 @@ class Jp2kBox(object):
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.longname = longname
|
self.longname = longname
|
||||||
|
|
||||||
|
# should never be used except for last box in file.
|
||||||
|
self._file_size = -1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = "{0} Box ({1})".format(self.longname, self.box_id)
|
msg = "{0} Box ({1})".format(self.longname, self.box_id)
|
||||||
msg += " @ ({0}, {1})".format(self.offset, self.length)
|
msg += " @ ({0}, {1})".format(self.offset, self.length)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def write(self, fptr):
|
def write(self, _):
|
||||||
"""Must be implemented in a subclass.
|
"""Must be implemented in a subclass.
|
||||||
"""
|
"""
|
||||||
msg = "Not supported for {0} box.".format(self.longname)
|
msg = "Not supported for {0} box.".format(self.longname)
|
||||||
|
|
@ -120,7 +125,7 @@ class Jp2kBox(object):
|
||||||
|
|
||||||
# Call the proper parser for the given box with ID "T".
|
# Call the proper parser for the given box with ID "T".
|
||||||
try:
|
try:
|
||||||
box = _BOX_WITH_ID[box_id]._parse(fptr, start, num_bytes)
|
box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
msg = 'Unrecognized box ({0}) encountered.'.format(box_id)
|
msg = 'Unrecognized box ({0}) encountered.'.format(box_id)
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
|
|
@ -177,6 +182,7 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
ICC profile header according to ICC profile specification. If
|
ICC profile header according to ICC profile specification. If
|
||||||
colorspace is not None, then icc_profile must be empty.
|
colorspace is not None, then icc_profile must be empty.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
|
def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0,
|
||||||
approximation=0, colorspace=None, icc_profile=None,
|
approximation=0, colorspace=None, icc_profile=None,
|
||||||
length=0, offset=-1):
|
length=0, offset=-1):
|
||||||
|
|
@ -203,8 +209,9 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
msg += '\n Precedence: {0}'.format(self.precedence)
|
msg += '\n Precedence: {0}'.format(self.precedence)
|
||||||
|
|
||||||
if self.approximation is not 0:
|
if self.approximation is not 0:
|
||||||
dispvalue = _approximation_display[self.approximation]
|
dispvalue = _APPROX_DISPLAY[self.approximation]
|
||||||
msg += '\n Approximation: {0}'.format(dispvalue)
|
msg += '\n Approximation: {0}'.format(dispvalue)
|
||||||
|
|
||||||
if self.colorspace is not None:
|
if self.colorspace is not None:
|
||||||
dispvalue = _COLORSPACE_MAP_DISPLAY[self.colorspace]
|
dispvalue = _COLORSPACE_MAP_DISPLAY[self.colorspace]
|
||||||
msg += '\n Colorspace: {0}'.format(dispvalue)
|
msg += '\n Colorspace: {0}'.format(dispvalue)
|
||||||
|
|
@ -240,7 +247,7 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
fptr.write(read_buffer)
|
fptr.write(read_buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse JPEG 2000 color specification box.
|
"""Parse JPEG 2000 color specification box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -468,7 +475,7 @@ class ChannelDefinitionBox(Jp2kBox):
|
||||||
self.association[j]))
|
self.association[j]))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse component definition box.
|
"""Parse component definition box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -542,7 +549,7 @@ class ComponentMappingBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse component mapping box.
|
"""Parse component mapping box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -607,7 +614,7 @@ class ContiguousCodestreamBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset=0, length=0):
|
def parse(fptr, offset=0, length=0):
|
||||||
"""Parse a codestream box.
|
"""Parse a codestream box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -685,7 +692,7 @@ class FileTypeBox(Jp2kBox):
|
||||||
fptr.write(item.encode())
|
fptr.write(item.encode())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse JPEG 2000 file type box.
|
"""Parse JPEG 2000 file type box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -812,7 +819,7 @@ class ImageHeaderBox(Jp2kBox):
|
||||||
fptr.write(read_buffer)
|
fptr.write(read_buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse JPEG 2000 image header box.
|
"""Parse JPEG 2000 image header box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -883,7 +890,7 @@ class AssociationBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse association box.
|
"""Parse association box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -956,7 +963,7 @@ class JP2HeaderBox(Jp2kBox):
|
||||||
fptr.seek(end_pos)
|
fptr.seek(end_pos)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse JPEG 2000 header box.
|
"""Parse JPEG 2000 header box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1017,7 +1024,7 @@ class JPEG2000SignatureBox(Jp2kBox):
|
||||||
fptr.write(struct.pack('>BBBB', *self.signature))
|
fptr.write(struct.pack('>BBBB', *self.signature))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse JPEG 2000 signature box.
|
"""Parse JPEG 2000 signature box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1073,7 +1080,7 @@ class PaletteBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse palette box.
|
"""Parse palette box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1220,6 +1227,8 @@ _READER_REQUIREMENTS_DISPLAY = {
|
||||||
+ 'requirements in M.9.2.3',
|
+ 'requirements in M.9.2.3',
|
||||||
73: 'YPbPr(1125/60) enumerated colourspace',
|
73: 'YPbPr(1125/60) enumerated colourspace',
|
||||||
74: 'YPbPr(1250/50) enumerated colourspace'}
|
74: 'YPbPr(1250/50) enumerated colourspace'}
|
||||||
|
|
||||||
|
|
||||||
class ReaderRequirementsBox(Jp2kBox):
|
class ReaderRequirementsBox(Jp2kBox):
|
||||||
"""Container for reader requirements box information.
|
"""Container for reader requirements box information.
|
||||||
|
|
||||||
|
|
@ -1276,7 +1285,7 @@ class ReaderRequirementsBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse reader requirements box.
|
"""Parse reader requirements box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1380,7 +1389,7 @@ class ResolutionBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Resolution box.
|
"""Parse Resolution box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1436,7 +1445,7 @@ class CaptureResolutionBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Resolution box.
|
"""Parse Resolution box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1493,7 +1502,7 @@ class DisplayResolutionBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Resolution box.
|
"""Parse Resolution box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1548,7 +1557,7 @@ class LabelBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Label box.
|
"""Parse Label box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1633,7 +1642,7 @@ class XMLBox(Jp2kBox):
|
||||||
fptr.write(read_buffer)
|
fptr.write(read_buffer)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse XML box.
|
"""Parse XML box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1697,7 +1706,7 @@ class UUIDListBox(Jp2kBox):
|
||||||
return(msg)
|
return(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse UUIDList box.
|
"""Parse UUIDList box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1760,7 +1769,7 @@ class UUIDInfoBox(Jp2kBox):
|
||||||
return(msg)
|
return(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse UUIDInfo super box.
|
"""Parse UUIDInfo super box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1828,7 +1837,7 @@ class DataEntryURLBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse Data Entry URL box.
|
"""Parse Data Entry URL box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
@ -1949,7 +1958,7 @@ class UUIDBox(Jp2kBox):
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse(fptr, offset, length):
|
def parse(fptr, offset, length):
|
||||||
"""Parse UUID box.
|
"""Parse UUID box.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ class Jp2k(Jp2kBox):
|
||||||
|
|
||||||
# Parse the file for JP2/JPX contents only if we are reading it.
|
# Parse the file for JP2/JPX contents only if we are reading it.
|
||||||
if mode == 'rb':
|
if mode == 'rb':
|
||||||
self._parse()
|
self.parse()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
metadata = ['File: ' + os.path.basename(self.filename)]
|
metadata = ['File: ' + os.path.basename(self.filename)]
|
||||||
|
|
@ -110,7 +110,7 @@ class Jp2k(Jp2kBox):
|
||||||
metadata.append(str(codestream))
|
metadata.append(str(codestream))
|
||||||
return '\n'.join(metadata)
|
return '\n'.join(metadata)
|
||||||
|
|
||||||
def _parse(self):
|
def parse(self):
|
||||||
"""Parses the JPEG 2000 file.
|
"""Parses the JPEG 2000 file.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
|
|
@ -425,7 +425,7 @@ class Jp2k(Jp2kBox):
|
||||||
_opj2.destroy_codec(codec)
|
_opj2.destroy_codec(codec)
|
||||||
_opj2.image_destroy(image)
|
_opj2.image_destroy(image)
|
||||||
|
|
||||||
self._parse()
|
self.parse()
|
||||||
|
|
||||||
def wrap(self, filename, boxes=None):
|
def wrap(self, filename, boxes=None):
|
||||||
"""Write the codestream back out to file, wrapped in new JP2 jacket.
|
"""Write the codestream back out to file, wrapped in new JP2 jacket.
|
||||||
|
|
|
||||||
47
release.txt
47
release.txt
|
|
@ -1,22 +1,25 @@
|
||||||
| OS | Python 2.7 | Python 3.3 | Notes |
|
| OS | Python 2.7 | Python 3.3 | Notes |
|
||||||
+------------+------------+------------+----------------------------------+
|
+------------+------------+------------+--------------------------------------+
|
||||||
| Windows | X | | Python(xy) with OpenJPEG 1.5.1. |
|
| Windows | X | | Python(xy) with OpenJPEG 1.5.1. |
|
||||||
| | | | At least 25 tests should pass |
|
| | | | At least 25 tests should pass |
|
||||||
+------------+------------+------------+----------------------------------+
|
+------------+------------+------------+--------------------------------------+
|
||||||
| Mac | X | | MacPorts with both OpenJPEG 1.5.1|
|
| Mac | X | | MacPorts with both OpenJPEG 1.5.1 |
|
||||||
| | | | and OpenJPEG svn. Maximum number|
|
| | | | and OpenJPEG svn. 370 of 450 tests |
|
||||||
| | | | of tests should pass. |
|
| | | | should pass. |
|
||||||
+------------+------------+------------+----------------------------------+
|
+------------+------------+------------+--------------------------------------+
|
||||||
| Mac | | X | MacPorts with both OpenJPEG 1.5.1|
|
| Mac | | X | MacPorts with both OpenJPEG 1.5.1 |
|
||||||
| | | | and OpenJPEG svn. Maximum number|
|
| | | | and OpenJPEG svn. 390 of 450 |
|
||||||
| | | | of tests should pass. |
|
| | | | tests should pass. |
|
||||||
+------------+------------+------------+----------------------------------+
|
+------------+------------+------------+--------------------------------------+
|
||||||
| Raspberry | X | | Ships with only 1.3. Should |
|
| Raspberry | X | | Ships with only 1.3. Should |
|
||||||
| | | | error out gracefully. |
|
| | | | error out gracefully. |
|
||||||
+------------+------------+------------+----------------------------------+
|
+------------+------------+------------+--------------------------------------+
|
||||||
| Fedora 17 | X | | Ships with 1.4. Should error out|
|
| Fedora 17 | X | | Ships with 1.4. Should error out |
|
||||||
| | | | gracefully. |
|
| | | | gracefully. |
|
||||||
+------------+------------+------------+----------------------------------+
|
+------------+------------+------------+--------------------------------------+
|
||||||
| Fedora 18 | | X | Ships with 1.5.1. Some tests |
|
| Fedora 18 | | X | Ships with 1.5.1. Some tests |
|
||||||
| | | | should pass. |
|
| | | | should pass. |
|
||||||
+------------+------------+------------+----------------------------------+
|
+------------+------------+------------+--------------------------------------+
|
||||||
|
|
||||||
|
Pylint on entire package should be at least 0.95.
|
||||||
|
pep8 should be pass cleanly.
|
||||||
|
|
|
||||||
3
setup.py
3
setup.py
|
|
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
kwargs = {'name': 'Glymur',
|
kwargs = {'name': 'Glymur',
|
||||||
'version': '0.1.10',
|
'version': '0.2.0',
|
||||||
'description': 'Tools for accessing JPEG2000 files',
|
'description': 'Tools for accessing JPEG2000 files',
|
||||||
'long_description': open('README.md').read(),
|
'long_description': open('README.md').read(),
|
||||||
'author': 'John Evans',
|
'author': 'John Evans',
|
||||||
|
|
@ -29,6 +29,7 @@ clssfrs = ["Programming Language :: Python",
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Operating System :: MacOS",
|
"Operating System :: MacOS",
|
||||||
"Operating System :: POSIX :: Linux",
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: Microsoft :: Windows :: Windows XP",
|
||||||
"Intended Audience :: Science/Research",
|
"Intended Audience :: Science/Research",
|
||||||
"Intended Audience :: Information Technology",
|
"Intended Audience :: Information Technology",
|
||||||
"Topic :: Software Development :: Libraries :: Python Modules"]
|
"Topic :: Software Development :: Libraries :: Python Modules"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue