scour-project / scour

Scour - An SVG Optimizer / Cleaner
Apache License 2.0
757 stars 61 forks source link

Feature request: call scour from Python code #290

Open rgoubet opened 2 years ago

rgoubet commented 2 years ago

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.

lfoscari commented 2 years ago

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.

rgoubet commented 2 years ago

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 commented 2 years ago

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.

Moonbase59 commented 2 years ago

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

brettrp commented 2 years ago

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.

rstemmer commented 2 years ago

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. 😄

mangelozzi commented 1 year ago

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 commented 1 year ago

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!