home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / moviedata.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  7.2 KB  |  198 lines

  1. # Miro - an RSS based video player application
  2. # Copyright (C) 2005-2007 Participatory Culture Foundation
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  17.  
  18. from eventloop import asIdle
  19. from platformutils import FilenameType
  20. import os.path
  21. import re
  22. import subprocess
  23. import time
  24. import traceback
  25. import threading
  26. import Queue
  27.  
  28. import app
  29. import eventloop
  30. import logging
  31. import config
  32. import prefs
  33. import util
  34.  
  35. MOVIE_DATA_UTIL_TIMEOUT = 60
  36. # Time in seconds that we wait for the utility to execute.  If it goes longer
  37. # than this, we assume it's hung and kill it.
  38. SLEEP_DELAY = 0.1
  39. # Time to sleep while we're polling the external movie command
  40.  
  41. durationRE = re.compile("Miro-Movie-Data-Length: (\d+)")
  42. thumbnailSuccessRE = re.compile("Miro-Movie-Data-Thumbnail: Success")
  43. thumbnailRE = re.compile("Miro-Movie-Data-Thumbnail: (Success|Failure)")
  44.  
  45. def thumbnailDirectory():
  46.     dir = os.path.join(config.get(prefs.ICON_CACHE_DIRECTORY), "extracted")
  47.     try:
  48.         os.makedirs(dir)
  49.     except:
  50.         pass
  51.     return dir
  52.  
  53.  
  54. class MovieDataInfo:
  55.     """Little utility class to keep track of data associated with each movie.
  56.     This is:
  57.  
  58.     * The item.
  59.     * The path to the video.
  60.     * Path to the thumbnail we're trying to make.
  61.     * List of commands that we're trying to run, and their environments.
  62.     """
  63.  
  64.     def __init__(self, item):
  65.         self.item = item
  66.         self.videoPath = item.getVideoFilename()
  67.         # add a random string to the filename to ensure it's unique.  Two
  68.         # videos can have the same basename if they're in different
  69.         # directories.
  70.         thumbnailFilename = '%s.%s.png' % (os.path.basename(self.videoPath),
  71.                 util.random_string(5))
  72.         self.thumbnailPath = os.path.join(thumbnailDirectory(),
  73.                 thumbnailFilename)
  74.         self.programInfo = []
  75.         if hasattr(app, 'controller') and hasattr(app.controller, 'videoDisplay'):
  76.             for renderer in app.controller.videoDisplay.renderers:
  77.                 try:
  78.                     commandLine, env = renderer.movieDataProgramInfo(
  79.                             self.videoPath, self.thumbnailPath)
  80.                 except NotImplementedError:
  81.                     pass
  82.                 else:
  83.                     self.programInfo.append((commandLine, env))
  84.  
  85. class MovieDataUpdater:
  86.     def __init__ (self):
  87.         self.inShutdown = False
  88.         self.queue = Queue.Queue()
  89.         self.thread = None
  90.  
  91.     def startThread(self):
  92.         self.thread = threading.Thread(name='Movie Data Thread',
  93.                 target=self.threadLoop)
  94.         self.thread.setDaemon(True)
  95.         self.thread.start()
  96.  
  97.     def threadLoop(self):
  98.         while not self.inShutdown:
  99.             movieDataInfo = self.queue.get(block=True)
  100.             if movieDataInfo is None:
  101.                 # shutdown() was called()
  102.                 break
  103.             try:
  104.                 duration = -1
  105.                 screenshotWorked = False
  106.                 screenshot = None
  107.                 for commandLine, env in movieDataInfo.programInfo:
  108.                     stdout = self.runMovieDataProgram(commandLine, env)
  109.                     if duration == -1:
  110.                         duration = self.parseDuration(stdout)
  111.                     if thumbnailSuccessRE.search(stdout):
  112.                         screenshotWorked = True
  113.                     if duration != -1 and screenshotWorked:
  114.                         break
  115.                 if (screenshotWorked and 
  116.                         os.path.exists(movieDataInfo.thumbnailPath)):
  117.                     screenshot = movieDataInfo.thumbnailPath
  118.                 else:
  119.                     # All the programs failed, maybe it's an audio file?
  120.                     # Setting it to "" instead of None, means that we won't
  121.                     # try to take the screenshot again.
  122.                     screenshot = FilenameType("")
  123.                 self.updateFinished(movieDataInfo.item, duration, screenshot)
  124.             except:
  125.                 if self.inShutdown:
  126.                     break
  127.                 util.failedExn("When running external movie data program")
  128.                 self.updateFinished(movieDataInfo.item, -1, None)
  129.  
  130.     def runMovieDataProgram(self, commandLine, env):
  131.         start_time = time.time()
  132.         pipe = subprocess.Popen(commandLine, stdout=subprocess.PIPE,
  133.                 stdin=subprocess.PIPE, stderr=subprocess.PIPE, env=env,
  134.                 startupinfo=util.no_console_startupinfo())
  135.         while pipe.poll() is None and not self.inShutdown:
  136.             time.sleep(SLEEP_DELAY)
  137.             if time.time() - start_time > MOVIE_DATA_UTIL_TIMEOUT:
  138.                 logging.info("Movie data process hung, killing it")
  139.                 self.killProcess(pipe.pid)
  140.                 return ''
  141.  
  142.         if self.inShutdown:
  143.             if pipe.poll() is None:
  144.                 logging.info("Movie data process running after shutdown, killing it")
  145.                 self.killProcess(pipe.pid)
  146.             return ''
  147.         return pipe.stdout.read()
  148.  
  149.     def killProcess(self, pid):
  150.         try:
  151.             app.delegate.killProcess(pid)
  152.         except:
  153.             logging.warn("Error trying to kill the movie data process:\n%s", traceback.format_exc())
  154.         else:
  155.             logging.info("Movie data process killed")
  156.  
  157.     def outputValid(self, stdout):
  158.         return (thumbnailRE.search(stdout) is not None and
  159.                 durationRE.search(stdout) is not None)
  160.  
  161.     def parseDuration(self, stdout):
  162.         durationMatch = durationRE.search(stdout)
  163.         if durationMatch:
  164.             return int(durationMatch.group(1))
  165.         else:
  166.             return -1
  167.  
  168.     @asIdle
  169.     def updateFinished (self, item, duration, screenshot):
  170.         if item.idExists():
  171.             item.duration = duration
  172.             item.screenshot = screenshot
  173.             item.updating_movie_info = False
  174.             item.resizeScreenshot()
  175.             item.signalChange()
  176.  
  177.     def requestUpdate (self, item):
  178.         if self.inShutdown:
  179.             return
  180.         filename = item.getVideoFilename()
  181.         if not filename or not os.path.isfile(filename):
  182.             return
  183.         if item.downloader and not item.downloader.isFinished():
  184.             return
  185.         if item.updating_movie_info:
  186.             return
  187.  
  188.         item.updating_movie_info = True
  189.         self.queue.put(MovieDataInfo(item))
  190.  
  191.     def shutdown (self):
  192.         self.inShutdown = True
  193.         self.queue.put(None) # wake up our thread
  194.         if self.thread is not None:
  195.             self.thread.join()
  196.  
  197. movieDataUpdater = MovieDataUpdater()
  198.