You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
calculate-utils-3-lib/pym/calculate/lib/format/backgrounds.py

264 lines
10 KiB

# -*- coding: utf-8 -*-
# Copyright 2017 Mir Calculate. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import os
import re
from ..cl_template import TemplateFormat
import hashlib
from ..cl_lang import setLocalTranslate
from ..utils.files import (getProgPath, pathJoin, process, STDOUT,
readFile, writeFile, listDirectory)
_ = lambda x: x
setLocalTranslate('cl_lib3', sys.modules[__name__])
class backgrounds(TemplateFormat):
"""
Формат для модификации принадлежности файлов пакетам
"""
text = ""
source = None
convert = None
prefix = None
stretch = False
mirror = False
fallback_md5fn = "md5sum"
def prepare(self):
self.chroot_cmd = None
self.chroot_path = None
self.convert_cmd = getProgPath('/usr/bin/convert')
self.bash_cmd = getProgPath('/bin/bash')
self.identify_cmd = getProgPath('/usr/bin/identify')
self.source_width = None
self.source_height = None
def textToXML(self):
return self.text
def setRootPath(self, rpath):
self.convert_cmd = '/usr/bin/convert'
self.identify_cmd = '/usr/bin/identify'
if not os.path.exists(pathJoin(rpath, self.convert_cmd)):
self.convert_cmd = None
if not os.path.exists(pathJoin(rpath, self.identify_cmd)):
self.identify_cmd = None
self.chroot_cmd = getProgPath('/bin/chroot')
self.chroot_path = rpath
def setMirror(self):
self.mirror = True
def setSource(self, source):
self.source = source
def setConvert(self, convert):
self.convert = convert
def setPrefix(self, prefix):
self.prefix = prefix
def getImageMD5(self, source, resolutions=()):
if source:
data = readFile(source, binary=True)
else:
data = b""
return hashlib.md5(data + "\n".join(resolutions).encode("UTF-8")).hexdigest()
def setStretch(self, stretch):
self.stretch = stretch
def trim_chroot_path(self, p):
return "/%s" % os.path.relpath(p, self.chroot_path)
def _create_image(self, source, target, width, height, force=False):
res = "%dx%d" % (width, height)
if not force and (width > self.source_width or
height > self.source_height):
return False
if self.convert != "gfxboot" and (
(width == self.source_width and height == self.source_height) and
(source.rpartition('.')[2] == target.rpartition('.')[2])):
with writeFile(target, binary=True) as sf:
sf.write(readFile(source, binary=True))
return True
if self.chroot_cmd:
source = self.trim_chroot_path(source)
target = self.trim_chroot_path(target)
command = [self.convert_cmd, "-quality", "95",
source, "-resize", "%s^" % res,
"-strip", "-gravity", "center",
"-crop", "%s+0+0" % res]
if self.convert == "gfxboot":
command.extend(
["-sampling-factor", "2x2",
"-interlace", "none",
"-set", "units", "PixelsPerSecond"])
command += [target]
if self.chroot_cmd:
convert = process(self.chroot_cmd, self.chroot_path,
self.bash_cmd, "-c",
" ".join(command), stderr=STDOUT)
else:
convert = process(*command, stderr=STDOUT)
convert.write(self.text)
if convert.success():
return True
return False
def get_image_resolution(self, source):
if self.chroot_cmd:
source = self.trim_chroot_path(source)
identify = process(self.chroot_cmd, self.chroot_path,
self.bash_cmd, "-c",
" ".join([self.identify_cmd,
"-format '%w %h'", source]))
else:
identify = process(self.identify_cmd, "-format", "%w %h", source)
if identify.success():
swidth, _sep, sheight = identify.read().strip().partition(" ")
if swidth.isdigit() and sheight.isdigit():
return int(swidth), int(sheight)
return None, None
return None, None
def processingFile(self, textConfigFile, rootPath=None, nameFile=None):
"""Обработка конфигурационного файла"""
if not (self.convert_cmd and self.identify_cmd):
self.setError("%s. %s" % (_("The 'backgrounds' format is unavailable"),
_("Need to install {packages}").format(
packages="virtual/imagemagick-tools")))
return False
if not self.source:
if not self.mirror:
return ""
if not self.convert:
self.convert = self.source.rpartition(".")[2].lower()
if self.convert not in self.objVar.Get('cl_image_formats'):
self.setError(_("Wrong image format '%s'") % self.convert)
return False
reRule = re.compile("^(\d+)x(\d+)(?:-[0-9]+)?$")
if not rootPath:
rootPath = '/'
if self.prefix is None:
workdir, prefix = os.path.split(nameFile)
else:
workdir = nameFile
prefix = ""
if prefix:
md5_fn = os.path.join(workdir, "%s.md5" %
re.sub("[-._]+$", "", prefix))
else:
md5_fn = os.path.join(workdir, self.fallback_md5fn)
if not self.source:
source = None
else:
source = os.path.normpath(pathJoin(rootPath, self.source))
if not os.path.exists(source):
source = None
else:
self.source_width, self.source_height = \
self.get_image_resolution(source)
if not self.source_width:
self.setError(
_("Failed to detect resolution for image '%s'") %
source)
return False
if self.text.strip():
text_list = self.text.split("\n")
else:
text_list = self.objVar.Get('cl_resolutions')
image_files = []
resolutions = [x.strip() for x in text_list
if not x.startswith("#") and x.strip()]
image_md5 = self.getImageMD5(source, resolutions=resolutions)
only_one = len(resolutions) == 1 and self.text.strip()
if only_one:
image_fn = pathJoin(workdir, prefix)
fn_base = image_fn.rpartition(".")[0]
if not fn_base:
fn_base = image_fn
md5_fn = "%s.md5" % fn_base
if os.path.exists(md5_fn):
md5sum = readFile(md5_fn).strip()
if md5sum == image_md5:
return ""
if only_one:
removed_files = [pathJoin(workdir, prefix)]
else:
removed_files = []
extensions = self.objVar.Get('cl_image_formats')
re_remove = re.compile(r"^%s\d{3,4}x\d{3,4}\.(%s)$"
% (prefix, "|".join(extensions)))
for remove_fn in listDirectory(workdir, fullPath=True):
fn = os.path.split(remove_fn)[1]
if re_remove.match(fn):
removed_files.append(remove_fn)
created = set()
if source:
for resolution in resolutions:
if resolution == "original":
resolution = "{}x{}".format(self.source_width, self.source_height)
rule_match = reRule.search(resolution)
if not rule_match:
self.setError(
_("Wrong 'backgrounds' resolution: %s") % resolution)
return False
width, height = rule_match.groups()
width = int(width)
height = int(height)
if (width,height) in created:
continue
else:
created.add((width,height))
if only_one:
image_fn = pathJoin(workdir, prefix)
else:
ext_map = {'gfxboot': 'jpg'}
image_fn = pathJoin(workdir, "%s%s.%s" % (
prefix, resolution,
ext_map.get(self.convert, self.convert)))
if self._create_image(source, image_fn, width, height,
force=self.stretch):
if image_fn in removed_files:
removed_files.remove(image_fn)
image_files.append(image_fn)
else:
self.parent.printWARNING(
_("Creation of image with %s resolution skipped") % (
"%dx%d" % (width, height)))
with writeFile(md5_fn) as f:
f.write("%s\n" % image_md5)
else:
removed_files.append(md5_fn)
self.changed_files.append(md5_fn)
for remove_fn in removed_files:
try:
if os.path.lexists(remove_fn):
os.unlink(remove_fn)
except (IOError, OSError):
self.parent.printWARNING(_("Failed to remove %s") % remove_fn)
self.changed_files.extend(image_files)
self.changed_files.extend(removed_files)
return ""