This repository has been archived by the owner on Jan 18, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
shopify_img_convert.py
executable file
·161 lines (140 loc) · 5.9 KB
/
shopify_img_convert.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/env python
"""Convert Shopify product images from PNG to JPG.
Requires the requests and ShopifyAPI library."""
# https://help.shopify.com/api/getting-started/authentication/private-authentication
# https://help.shopify.com/api/reference/product
# https://help.shopify.com/api/reference/product_image
# https://github.com/Shopify/shopify_python_api
import sys
import os
import os.path
import subprocess
import shopify
try:
from ConfigParser import SafeConfigParser # Python 2
except ImportError:
from configparser import SafeConfigParser # Python 3
import requests
CONFIG = SafeConfigParser()
CONFIG.read('shopify_img_convert.ini')
CONFIG_MAIN = dict(CONFIG.items('main'))
def auth(store=None, api_key=None, password=None):
"""Set authentication details for shopify."""
store = store or CONFIG_MAIN['store']
api_key = api_key or CONFIG_MAIN['api_key']
password = password or CONFIG_MAIN['password']
shop_url = "https://%s:%s@%s/admin" % (api_key, password, store)
shopify.ShopifyResource.set_site(shop_url)
def get_products():
"""Get all product objects for the whole store."""
num_prods = 1
products = []
page = 1
while num_prods:
prods = shopify.Product.find(page=page, limit=250)
products.extend(prods)
num_prods = len(prods)
page += 1
return products
def is_png(url):
"""Does the given URL point to a PNG file?"""
return requests.head(url).headers['content-type'] == 'image/png'
def convert_images_for_product(product, path=".", quiet=False):
"""Given a product object or id number, convert all PNGs into JPGs."""
# A product object was given
try:
product_id = product.id
# A product ID was given
except AttributeError:
product_id = product
product = shopify.Product(shopify.Product.get(product_id))
# we need a shallow copy to iterate over below (so when we edit
# product.images we're not also editing this list while in the loop.)
images = [i for i in product.images]
if not os.path.isdir(path):
os.mkdir(path)
path_product = os.path.join(path, str(product_id))
if not os.path.isdir(path_product):
os.mkdir(path_product)
for image in images:
if not quiet:
sys.stderr.write('%s: ' % image.src)
if not is_png(image.src):
if not quiet:
sys.stderr.write('not a PNG, skipping.\n')
continue
if not quiet:
sys.stderr.write('converting...\n')
r = requests.get(image.src)
if not r.status_code == 200:
raise Exception('Error getting %s' % image.src)
filename_png = os.path.basename(r.links['canonical']['url'])
filename_jpg = os.path.splitext(filename_png)[0]+'.jpg'
path_png = os.path.join(path_product, filename_png)
path_jpg = os.path.join(path_product, filename_jpg)
path_json = os.path.join(path_product, str(image.id)+'.json')
# stash the original content, just for safety
with open(path_json, 'w') as f:
if not quiet:
sys.stderr.write(' saving JSON\n')
f.write(image.to_json())
with open(path_png, 'w') as f:
if not quiet:
sys.stderr.write(' saving PNG\n')
f.write(r.content)
# create new image file
cmd = ['convert', path_png, '-quality', '90%', path_jpg]
if not quiet:
sys.stderr.write(' executing: %s ... ' % ' '.join(cmd))
output = subprocess.check_output(cmd)
if not quiet:
sys.stderr.write('%s\n' % output)
# read in the new image data and attach to image object
with open(path_jpg) as f:
jpg_data = f.read()
if jpg_data[0:2] != '\xff\xd8':
raise Exception('JPEG prefix not found when loading %s' % path_jpg)
# Careful, now. If I do this, the image will have the right content,
# but the content-type delivered by Shopify will still be image/png.
# The safer way looks to be to create a new image object and add that
# to the product's list of images, keeping the position value the same.
#
# image.attach_image(data=jpg_data, filename=os.path.basename(path_jpg))
# image.save()
# So, safely add a new image, and remove the old one.
# We use the original attributes and then remove what we don't want
# rather than explicitly adding items so that we don't inadvertantly
# miss anything (say, variant_ids).
attrs = {}
attrs.update(image.attributes)
del attrs['id']
del attrs['src']
image_new = shopify.Image(attributes=attrs)
image_new.attach_image(data=jpg_data, filename=os.path.basename(path_jpg))
# TODO: reading image position from the object's attribute is not
# reliable; I just saw a case with four images where the positions were
# 1,2,4,4. Maybe instead just redo this whole mess to loop over index
# values instead.
pos = attrs['position'] - 1
if not quiet:
sys.stderr.write(' replacing image object %d\n' % product.images[pos].id)
del product.images[pos]
product.images.insert(pos, image_new)
# Apparently save() is what actually sends the updated data to the server.
product.save()
def convert_all_products(quiet=False, path=None):
"""Convert all PNG images for all products in JPGs.
quiet: should status information be written to standard error? (False by default)
path: subdirectory to use for stash of original data ("." by default)
"""
products = get_products()
path = path or CONFIG_MAIN['store']
for product in products:
if not quiet:
sys.stderr.write('>>> Product %d:\n' % product.id)
convert_images_for_product(product, path, quiet)
def main(args):
auth()
convert_all_products()
if __name__ == "__main__":
main(sys.argv)