Книга: Написание скриптов для Blender 2.49

В Сеть и дальше - публикация готового рендера на FTP

В Сеть и дальше - публикация готового рендера на FTP

Мы можем сохранить отрендеренное изображение в любое место, видимое в файловой системе, но не все платформы дают возможность сделать удалённый FTP-сервер доступным через локальный каталог. Этот скрипт предлагает нам простую опцию, позволяющую загружать отрендеренное изображение на удалённый FTP-сервер, и запоминает имя сервера, имя пользователя, и (необязательно) пароль, чтобы позже использовать их снова.

File Transfer Protocol (FTP) (Протокол передачи файлов), который мы будем использовать, несколько сложнее, чем, например, протокол HTTP, так как он использует больше одной связи. К счастью для нас, все сложности FTP-клиента хорошо изолированы в стандартном модуле Питона ftplib. Мы не только импортируем класс FTP этого модуля, но также множество других стандартных модулей Питона, особенно для обработки путей файлов (os.path) и для чтения файлов стандарта .netrc (который позволит нам сохранять пароли за пределами нашего скрипта, если нам нужны пароли для регистрации на FTP-сервере). Мы обсудим каждый модуль, когда понадобится.

from ftplib import FTP
import os.path
import re
import netrc
import tempfile
from Blender import Image,Registry,Draw

Питон изначально является почти платформонезависимым, но, конечно, иногда встречаются сложности, которые не полностью охвачены. Например, мы хотим использовать имена пользователя и пароли, сохраненные в файле .netrc, который обычно используется программами FTP (и другими), и FTP-клиент ожидает, что этот файл будет находиться в домашнем каталоге пользователя, который он надеется найти в переменной окружения HOME. На Windows, тем не менее, понятие домашнего каталога не так хорошо определено, и существуют различные схемы для сохранения данных, которые ограничиваются единственным пользователем; не каждая реализация Питона справляется с этим одинаковым образом.

Следовательно, мы определяем небольшую функцию-утилиту, которая проверяет наличие переменной HOME в окружении (она всегда есть на Unix-подобных операционных системах, и на некоторых версиях Windows). Если таковой нет, она проверяет наличие переменной USERPROFILE (присутствует в большинстве версий Windows, включая XP, где она обычно указывает на каталог C:Documents и Settings<имя пользователя>). Если она присутствует, функция устанавливает переменную HOME в значение, содержащееся в этой переменной USERPROFILE:

def sethome():
   from os import environ
   if not 'HOME' in environ:
      if 'USERPROFILE'in environ:
         environ['HOME'] = environ['USERPROFILE']

Наша следующая задача в том, чтобы выяснить, на какой FTP-сервер пользователь хочет загрузить результат рендера. Мы запоминаем это в ключе реестра Блендера, чтобы не надоедать пользователю с приглашением всякий раз, когда он хочет отправить свой рендер. Функция getftphost() принимает аргумент reuse (повторное использование), который может быть использован для очистки этого ключа, если он установлен в False (для обеспечения возможности выбора другого FTP-сервера), но переписать интерфейс пользователя, чтобы предлагать ему такую возможность, мы оставляем в качестве упражнения читателю.

Фактический код начинает с поиска ключа реестра (с диска, если необходимо, следовательно, по умолчанию аргумент True, выделено). Если там нет ключа, или он не содержит имя сервера, мы запрашиваем у пользователя имя FTP-сервера посредством всплывающего окна. Если пользователь его не вводит, мы заканчиваем, возбуждая исключение. В противном случае, мы сохраняем имя хоста - сначала создаём словарь, если он ещё не существует, и сохраняем этот словарь в реестр Блендера. Наконец, мы возвращаем сохранённое имя хоста.

def getftphost(reuse=True):
   dictname = 'ftp'
   if reuse == False:
      Registry.RemoveKey(dictname)
   d = Registry.GetKey(dictname,True)
   if d == None or not 'host' in d:
      host = Draw.PupStrInput("Ftp hostname:", "", 45)
      if host == None or len(host) == 0 :
         raise Exception("no hostname specified")
      if d == None :
         d ={}
      d['host'] = host
      Registry.SetKey(dictname,d,True)
   return d['host']

Нам нужна другая вспомогательная функция, чтобы убедиться, что на диск в качестве изображения Блендера сохранено последнее отрендеренное изображение, которое присутствует как изображение с именем  Render Result, но это изображение не пишется на диск автоматически. Функция imagefilename() принимает изображение Блендера как аргумент, и, во-первых, проверяет, существует ли корректное имя файла, связанное с ним (выделено). Если нет, она создает имя файла из имени изображения, добавляя расширение .tga (изображения можно сохранять только как файлы TARGA). Затем создаётся полный путь из этого имени файла и пути к временному каталогу. Теперь, когда у нас есть корректное имя файла, мы его сохраняем, вызывая метод save(), и возвращая имя файла:

def imagefilename(im):
   filename = im.getFilename()
   if filename == None or len(filename) == 0:
      filename = im.getName()+'.tga'
      filename = os.path.join(tempfile.gettempdir(),
                              filename)
      im.setFilename(filename)
   im.save()
   return filename

Когда мы загружаем файл на FTP-сервер, мы хотим убедиться, что мы не перезапишем существующий файл. Если мы обнаружим, что файл с данным именем уже существует, мы хотели бы иметь функцию, которая создаёт новое имя файла предсказуемым способом, похожим на то, как ведёт себя Блендер при создании имён для объектов Блендера. Мы хотели бы сохранять расширение файла, так что мы не можем просто прилепить к имени цифровой суффикс. Функция nextfile(), следовательно, сначала разделяет имя пути и часть с расширением. Она использует функции split() и splitext() из модуля os.path, чтобы оставить нам чистое имя.

Если имя уже заканчивается на суффикс, состоящий из точки и некоторого числа (например, .42), мы хотели бы увеличить это число. Это именно то, что выполняет довольно пугающая выделенная строка. Функция sub() модуля Питона re принимает регулярное выражение как первый аргумент (мы используем здесь сырую строку, так что нам не надо экранировать обратную косую черту), и проверяет, соответствует ли это регулярное выражение своему третьему аргументу (name, в данном случае). Регулярное выражение, используемое здесь, (.(d+)$) совпадает с точкой, за которой следуют одна или более десятичных цифр, но только, если эти цифры являются последними символами. Если есть соответствие образцу, он заменяется вторым аргументом функции sub(). В нашем случае, замена - это не простая строка, а лямбда-функция (то есть, безымянная), в которую мы передаём объект сопоставления, и ожидаем, что она вернёт строку.

Мы окружили часть цифр нашего регулярного выражения круглыми скобками, теперь мы можем просто извлечь эти цифры (без первоначальной точки), вызвав метод group() объекта сопоставления. Мы передаем ему 1 в качестве аргумента, так как первые открывающие скобки обозначают первую группу (группа 0 является всем образцом целиком). Мы преобразуем эту строку цифр в целое, используя встроенную функцию int(), добавляем к ней 1, и преобразуем её обратно в строку с функцией str(). До того, как этот результат автоматически будет возвращён из лямбда-функции, мы снова добавляем точку, чтобы соответствовать нашему желаемому образцу.

Мы завершаем проверкой, отличается ли результирующее имя от оригинального.  Если они совпадают, значит оригинальное имя не соответствовало нашему образцу, и мы просто добавляем .1 к имени. Наконец, мы восстанавливаем полное имя файла, добавляя расширение, и вызывая функцию join() из модуля os.path, чтобы добавить путь платформо-независимым способом:

def nextfile(filename):
   (path,base) = os.path.split(filename)
   (name,ext) = os.path.splitext(base)
   new = re.sub(r'.(d+)$',
                lambda m:'.'+ str(1+int(m.group(1))),
                name)
   if new == name :
      new = name + '.1'
   return os.path.join(path,new+ext)

Теперь мы полностью готовы заняться реальной работой загрузки файла на FTP-сервер. Сначала мы удостоверимся, что наше окружение имеет переменную HOME, вызывая функцию sethome(). Затем, мы извлекаем имя хоста FTP-сервера, на который мы хотим загрузить (вполне законно, между прочим, ввести IP-адрес вместо имени хоста):

if __name__ == "__main__":
   sethome()
   host = getftphost()

Далее, мы извлекаем данные учётной записи пользователя для выбранного хоста из файла .netrc, если он присутствует (выделено). Это может закончиться неудачей по различным причинам (могло не быть .netrc-файла, или данные хоста отсутствуют в файле); в этом случае будет возбуждено исключение. Если это случится, мы сообщаем об этом пользователю и требуем имя пользователя и пароль с помощью всплывающего окна:

   try:
      (user,acct,password) =
              netrc.netrc().authenticators(host)
   except:
      acct=None
      user = Draw.PupStrInput(
             'No .netrc file found, enter username:',
             "",75)
      password = Draw.PupStrInput('Enter password:',"",75)

Отрендеренное изображение было сохранено как объект Блендера Image с именем Render Result. Следующая вещь, которую мы делаем - извлекаем ссылку на это изображение и убеждаемся, что оно сохранено на диск. Функция imagefilename(), которую мы определили раньше, возвращает имя файла загруженного изображения.

Следующим шагом нужно подключиться к FTP-серверу, используя имя хоста и данные учётной записи, которые мы извлекли раньше (выделено). Как только связь будет установлена, мы извлекаем список имён файлов с помощью метода nlst():

   im = Image.Get('Render Result')
   filename = imagefilename(im)
   ftp = FTP(host,user,password,acct)
   files = ftp.nlst()

Хм, автор так аккуратно обрабатывает ситуации отсутствия  файла .netrc, имени с паролем в нём, сохранённости рендеренного изображения, а о работоспособности FTP-сервера вообще не упоминает. По-моему, ситуацию отсутствия связи, а также неверности логина или пароля тоже необходимо обрабатывать через  try/except. - прим. пер.

Поскольку мы хотим убедиться, что мы не перезаписываем никаких файлов на FTP-сервере, мы удаляем путь из имени файла нашего загруженного изображения с помощью функции basename() и сравниваем результат со списком имён файлов, извлеченным с сервера (выделено). Если имя файла уже присутствует, мы генерируем новое имя функцией nextfile() и снова проверяем, и продолжаем проверять, пока у нас, наконец, не появится имя файла, которое в данный момент отсутствует на FTP-сервере.

   dstfilename = os.path.basename(filename)
   while dstfilename in files:
      dstfilename = nextfile(dstfilename)

Затем, мы выгружаем наш файл изображения, вызывая метод storbinary(). Этот метод принимает имя целевого файла с префиксом STOR, как первый аргумент, и открытый файловый дескриптор как второй аргумент. Мы предоставляем последний, вызывая встроенную функцию Питона open() с именем нашего файла изображения в качестве единственного аргумента. (Если нужна дополнительная информация о довольно диковинном поведении модуля ftplib, ссылка на его документацию: http://docs.python.org/library/ftplib.html.) Мы грациозно заканчиваем связь в FTP-сервером, вызывая метод quit(), и сообщаем пользователю о завершении задачи, показывая сообщение с упоминанием имени целевого файла, так как оно может отличаться от ожидаемого, если существует файл с аналогичным именем:

   ftp.storbinary('STOR '+dstfilename,open(filename))
   ftp.quit()
   Draw.PupMenu('Render result stored as "%s"%s|Ok'
                 %(dstfilename,'%t'))

Полный код доступен как ftp.py в файле ftp.blend. Его можно запустить из текстового редактора, но в общем случае, несомненно, значительно удобнее поместить ftp.py в каталог скриптов Блендера. Скрипт сконфигурирован так, чтобы он был доступен в меню Файл | Экспорт (File | Export).

Оглавление книги


Генерация: 1.176. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз