scour icon indicating copy to clipboard operation
scour copied to clipboard

Feature request: call scour from Python code

Open rgoubet opened this issue 2 years ago • 8 comments

I'm sure it must be possible to call scour, along with the various command-line flags, within Python code, which is interesting to optimize svg files generated by a Python program. From what I can tell, the XML string is passed to the scourString function, but I can't make out how flags are passed as options.

It's probably a matter of documentation, but, IMO, it would be worth adding a note on this and not limit scour's usage to the command line.

rgoubet avatar Jan 31 '22 14:01 rgoubet

I think I found a way to call scour from Python code:

from scour import scour
import sys

sys.argv = ["-i", "input.svg", "-o", "output.svg"]
scour.run()

The run function uses optparse in the background, which reads the command line arguments directly.

lfoscari avatar Feb 05 '22 19:02 lfoscari

Yes, of course you can always call the script itself, but it would be much clearer if one could use the scourString method itself.

rgoubet avatar Feb 06 '22 18:02 rgoubet

At least, this seems to work to optimize an XML string (i.e. without a need for an SVG file on disk):

from scour import scour

def minify_svg(xml_string):
    opts = scour.sanitizeOptions(
        {
            "digits": 5,
            "quiet": False,
            "verbose": False,
            "cdigits": -1,
            "simple_colors": True,
            "style_to_xml": True,
            "group_collapse": True,
            "group_create": False,
            "keep_editor_data": False,
            "keep_defs": False,
            "renderer_workaround": True,
            "strip_xml_prolog": False,
            "remove_titles": True,
            "remove_descriptions": True,
            "remove_metadata": True,
            "remove_descriptive_elements": True,
            "strip_comments": True,
            "embed_rasters": True,
            "enable_viewboxing": True,
            "indent_type": "none",
            "indent_depth": 1,
            "newlines": True,
            "strip_xml_space_attribute": False,
            "strip_ids": True,
            "shorten_ids": True,
            "shorten_ids_prefix": "",
            "protect_ids_noninkscape": False,
            "protect_ids_list": None,
            "protect_ids_prefix": None,
            "error_on_flowtext": False,
        }
    )
    return scour.scourString(xml_string, opts)

All options in the opts dictionary are probably not necessary (I understand that the sanitizeOptions function sets the default option values), but I'd need to run trial and error with each of them to understand their meaning compared to the command line options.

rgoubet avatar Feb 08 '22 20:02 rgoubet

Scour with an "official" module API would be nice, yes. +1 Calling it as a subprocess from another Python app feels so suboptimal…

Moonbase59 avatar Feb 14 '22 22:02 Moonbase59

This works fine for me:

    scour_options_dict = {'strip_comments': True, 'indent_type': 'none', 'shorten_ids': True, 'enable_viewboxing': True, 'strip_ids': True, 'strip_xml_prolog': True, 'remove_metadata': True}
    scour_options = SimpleNamespace(**scour_options_dict)
    svg_compressed = scourString(svg, scour_options)

The only real problem I can see with that is that it's undocumented, and the names of the keys are not exactly the same as the names of the command line options, so in theory they could change.

brettrp avatar Apr 01 '22 02:04 brettrp

I additionally imported the parse_args function. You can give the command line arguments as list of string to that function. The function then returns a valid options-dictionary.

from scour.scour import scourString
from scour.scour import sanitizeOptions as sanitizeScourOptions
from scour.scour import parse_args as parseScourArgs

def Optimize(sourcesvg):
    scouroptions = parseScourArgs([
        "--enable-id-stripping",
        "--enable-comment-stripping",
        "--shorten-ids",
        "--indent=none",
        "--no-line-breaks"])
    scouroptions = sanitizeScourOptions(scouroptions)
    optimizedsvg = scourString(sourcesvg, scouroptions)
    return optimizedsvg

(Source)

Of course, as long as this is not an official and documented feature to use Scour functions inside other Python tools, our code can break with any update. 😄

rstemmer avatar May 14 '22 10:05 rstemmer

Another usecase is for a web server, one especially does not want the overhead/complexity writing to disk (multiple workers) and reading from disk. You generally want to keep everything in memory, in which case an API would be great.

mangelozzi avatar Aug 26 '22 06:08 mangelozzi

I additionally imported the parse_args function. You can give the command line arguments as list of string to that function. The function then returns a valid options-dictionary.

from scour.scour import scourString
from scour.scour import sanitizeOptions as sanitizeScourOptions
from scour.scour import parse_args as parseScourArgs

def Optimize(sourcesvg):
    scouroptions = parseScourArgs([
        "--enable-id-stripping",
        "--enable-comment-stripping",
        "--shorten-ids",
        "--indent=none",
        "--no-line-breaks"])
    scouroptions = sanitizeScourOptions(scouroptions)
    optimizedsvg = scourString(sourcesvg, scouroptions)
    return optimizedsvg

(Source)

Of course, as long as this is not an official and documented feature to use Scour functions inside other Python tools, our code can break with any update. smile

This is propably the most resilent solution so far, nice one!

mangelozzi avatar Aug 26 '22 06:08 mangelozzi