Package toon :: Module save
[hide private]
[frames] | no frames]

Source Code for Module toon.save

  1  #!/usr/bin/env python 
  2  # 
  3  # Toonloop for Python 
  4  # 
  5  # Copyright 2008 Alexandre Quessy & Tristan Matthews 
  6  # <alexandre@quessy.net> & <le.businessman@gmail.com> 
  7  # http://www.toonloop.com 
  8  # 
  9  # Original idea by Alexandre Quessy 
 10  # http://alexandre.quessy.net 
 11  # 
 12  # Toonloop is free software: you can redistribute it and/or modify 
 13  # it under the terms of the GNU General Public License as published by 
 14  # the Free Software Foundation, either version 3 of the License, or 
 15  # (at your option) any later version. 
 16  # 
 17  # Toonloop is distributed in the hope that it will be useful, 
 18  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 19  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 20  # GNU General Public License for more details. 
 21  # 
 22  # You should have received a copy of the gnu general public license 
 23  # along with Toonloop.  If not, see <http://www.gnu.org/licenses/>. 
 24  # 
 25  """ 
 26  Saves the images and movie file from a clip. 
 27  """ 
 28  import sys 
 29  from time import strftime 
 30  import os 
 31  import shutil 
 32  import glob 
 33  import pprint 
 34   
 35  from twisted.internet import reactor 
 36  from twisted.internet import defer 
 37  #from twisted.python import failure 
 38   
 39  from toon import mencoder 
 40  from rats import sig 
 41  import pygame 
 42  from pygame.locals import * 
 43   
 44  PROGRESS_MKDIR = 0.05 
 45  PROGRESS_IMAGES = 0.85 
 46  PROGRESS_MENCODER = 0.05 
 47  PROGRESS_CLEANUP = 0.05 
 48   
49 -class ClipSaverError(Exception):
50 """ 51 Any error that may occur during saving a clip. 52 """ 53 pass
54
55 -class ClipSaver(object):
56 """ 57 Saves a clip to images and a movie file if possible. 58 """ 59 # TODO: save using threads. 60 # TODO: use rats.statesaving to save state 61 # TODO: load state and files as well
62 - def __init__(self, core, dir_path, file_prefix, clip_id):
63 """ 64 :param core: The Toonloop application object. 65 :param dir_path: Path to the directory where to save the clip. 66 :param file_prefix: The beginning of the name of the files to save. 67 :param clip_id: The id of the clip to save. 68 """ 69 self.clip_id = clip_id 70 self.core = core # toonloop app 71 self.file_prefix = file_prefix 72 self.dir_path = dir_path 73 self.current_index = 0 # saving which image next 74 self.IMAGES_DIR = "images" 75 self._deferred = None 76 self.is_busy = False 77 self.signal_progress = sig.Signal() # arg: float from 0 to 1 78 self.signal_done = sig.Signal() # arg: boolean success
79
80 - def save(self):
81 """ 82 Does save the clip. 83 Return a deferred. 84 """ 85 self.is_busy = True 86 if self._deferred is not None: 87 raise ClipSaverError("Do not call save twice on the same ClipSaver.") 88 self._deferred = defer.Deferred() 89 try: 90 if not os.path.exists(self.dir_path): 91 os.makedirs(self.dir_path) 92 print('mkdir %s' % (self.dir_path)) 93 except OSError, e: 94 msg = 'Error creating directories' % (self.dir_path, e.message) 95 self._fail(msg) 96 print(msg) 97 else: 98 try: 99 data_subdir = os.path.join(self.dir_path, self.IMAGES_DIR) 100 if not os.path.exists(data_subdir): 101 os.makedirs(data_subdir) 102 print('mkdir %s' % (data_subdir)) 103 except OSError, e: 104 msg = 'Error creating directories' % (data_subdir, e.message) 105 print(msg) 106 self._fail(msg) 107 else: 108 self.signal_progress(PROGRESS_MKDIR) 109 reactor.callLater(0.0, self._write_01_next_image) 110 return self._deferred
111
112 - def _fail(self, msg):
113 self.is_busy = False 114 self.signal_done(False) 115 self._deferred.errback(msg)
116
117 - def _succeed(self, msg):
118 self.is_busy = False 119 self.signal_done(True) 120 self._deferred.callback(msg)
121
122 - def _write_01_next_image(self):
123 """ 124 Saves each image using twisted in order not to freeze the app. 125 Uses the JPG extension. 126 """ 127 # TODO : use clip_id 128 num_images_in_clip = len(self.core.clips[self.clip_id].images) 129 if self.current_index < num_images_in_clip: 130 name = ("%s/%s_%5d.jpg" % (self.dir_path, self.file_prefix, self.current_index)).replace(' ', '0') 131 if self.core.config.verbose: 132 print("writing image %s" % (self.current_index)) 133 pygame.image.save(self.core.clips[self.clip_id].images[self.current_index], name) # filename extension makes it a JPEG 134 self.current_index += 1 135 self.signal_progress(PROGRESS_MKDIR + PROGRESS_IMAGES * self.current_index / float(num_images_in_clip)) 136 reactor.callLater(0.0, self._write_01_next_image) 137 else: 138 reactor.callLater(0.0, self._write_02_images_done)
139
140 - def _write_02_images_done(self):
141 """ 142 Converts the list of images in a motion-JPEG .mov video file. 143 """ 144 if self.current_index > 0: 145 if self.core.config.verbose: 146 print("\nConverting to motion JPEG in Quicktime container.") 147 fps = 12 # TODO FIXME 148 #self.clip.increment_every # self.clip.framerate 149 #fps = self.renderer.desired_fps 150 deferred = mencoder.jpeg_to_movie(self.file_prefix, self.dir_path, fps, self.core.config.verbose, self.core.config.image_width, self.core.config.image_height) 151 deferred.addCallback(self._write_03_movie_done) 152 deferred.addErrback(self._eb_mencoder)
153 # to do : serialize clips with file names 154 # self.project_file = 'project.txt' 155
156 - def _eb_mencoder(self, reason):
157 msg = reason.getErrorMessage() 158 print(msg) 159 self._fail(msg)
160
161 - def _write_03_movie_done(self, results): # deferred callback
162 """ 163 Called when mencoder conversion is done. 164 MOV file. 165 """ 166 if self.core.config.verbose: 167 print("Done converting %s/%s.mov" % (self.dir_path, self.file_prefix)) 168 #self.signal_progress(PROGRESS_MKDIR + PROGRESS_IMAGES + PROGRESS_MENCODER) 169 self.signal_progress(1.0) # done (more satisfying right now) 170 reactor.callLater(1.0, self._write_04_delete_images)
171
172 - def _write_04_delete_images(self):
173 """ 174 deletes JPG images or moves them to the 'data' folder in the project folder. 175 renames MOV file. 176 """ 177 files = glob.glob("%s/%s_*.jpg" % (self.dir_path, self.file_prefix)) 178 for f in files: 179 if self.core.config.delete_jpeg: # delete images 180 try: 181 os.remove(f) 182 if self.core.config.verbose: 183 print('removed %s' % (f)) 184 except OSError, e: 185 msg = "%s Error removing file %s" % (e.message, f) 186 print(msg) 187 #self._fail(msg) 188 else: # move images 189 try: 190 dest = os.path.join(self.dir_path, self.IMAGES_DIR, os.path.basename(f)) 191 shutil.move(f, dest) 192 #if self.core.config.verbose: 193 # print('moved %s to %s' % (f, dest)) 194 except IOError, e: 195 msg = "%s Error moving file %s to %s" % (e.message, f, dest) 196 print(msg) 197 #self._fail(msg) 198 # rename final movie file 199 try: 200 src = "%s/%s.mov" % (self.dir_path, self.file_prefix) 201 dest = "%s/clip_%s.mov" % (self.dir_path, self.file_prefix) 202 shutil.move(src, dest) 203 if self.core.config.verbose: 204 print('renamed %s to %s' % (src, dest) ) 205 print('DONE SAVING CLIP %s' % (self.clip_id)) 206 except IOError, e: 207 msg = "%s Error moving file %s to %s" % (e.message, src, dest) 208 print(msg) 209 self._fail(msg) 210 else: 211 self.signal_progress(1.0) # done 212 self._succeed("Successfully saved images, converted movie and moved files for clip %s" % (self.clip_id))
213