def transcodeOneVideo(self, video): if video.already_transcoded or video in self.transcoded_files: logging.info("Already transcoded %s" % video.url) return False logging.info("Transcoding %s" % video.url) self._status["current_video"] = str(video) (result, transcoded_filename) = self.transcodeFile(video.local_filename) self._status["current_video"] = None del self._status["progress"] if self.must_die_event.isSet(): return False if result: video.local_transcoded_filename = transcoded_filename video.already_transcoded = True # Inutile, mais on ne sait jamais... video.already_downloaded = True # On supprime le fichier flv, il ne sert plus a rien... os.remove(video.local_filename) # Si on est sur Darwin (OS X), signale le succes avec Growl if platform.system() == "Darwin": subprocess.call([findSettings()["growlnotify"], "-n", "PodcastCanalPlus", "-t", "Encodage Fini", "-m", video.name]) return True else: return False
def transcodeFile(self, video_filename): waiting_videos_count = self.transcode_queue.qsize() handbrake_options = ( "--preset '%s'" % findSettings()["transcode_preset"]) handbrake_cli_path = findSettings()["handbrake"] command_template = "%s %s --input '%s' --output '%s'" transcoded_filename = "" try: transcoded_filename = os.path.basename(video_filename) transcoded_filename = transcoded_filename.replace(' ', '_') try: extension_index = transcoded_filename.rindex(".") transcoded_filename = transcoded_filename[:extension_index] + ".m4v" except ValueError, e: transcoded_filename = transcoded_filename[:-4] + ".m4v" except TypeError, e: pprint.pprint(video_filename) return (False, None)
def getJsonString(self, ip_address): """Renvoie un flux JSON correspondant a une adresse IP du serveur donnee. L'adresse IP du serveur n'est pas fixe, car : - Le serveur peut en changer - Les differents clients peuvent acceder au serveur par une adresse differente. ex: acces par le reseau local et internet par NAT. Args: ip_address: L'adresse IP a preciser dans le flux, sous forme de string. """ self.addAllAvailableVideos() podcast_address = findSettings()["podcast_address_template"] % ip_address base_url = "%s:%d/" % (podcast_address, findSettings()["podcast_port"]) podcast_dict = { "info": {"title": self.title, "link": base_url + self.pipeline_name + ".xml", "description": self.description, "lastBuildDate": int(time.time()), }, "items": [], } for (video, size, change_time) in self.podcasted_videos: filename = video.local_transcoded_filename url = base_url + filename.split('/')[-1] if video.description is not None: description = video.description else: description = filename change_datetime = datetime.datetime.fromtimestamp(change_time) if video.name is not None: name = video.name else: name = filename video_enclosure = RSS2.Enclosure(url, size, "video/x-m4v") item = {"title": name, "description": description, "video_url": url, "video_size": size, "mime": "video/x-m4v", "date": change_time,} podcast_dict["items"].append(item) return json.dumps(podcast_dict)
def createPipelines(self): self.threads = [] logging.info("Demarrage des pipelines...") # Recupere les infos sur ce qui a deja ete fait depuis le disque dur self.downloaded_files = SynchronizedSet('downloaded_videos') self.transcoded_files = SynchronizedSet('transcoded_videos') if self.options.verbose: # Affichage de ce qu'on a deja fait print "Downloaded Videos:" for video in self.downloaded_files: print video print "Transcoded Videos:" for video in self.transcoded_files: print video # Serveur HTTP - un seul pour tous les pipelines self.http_server = PodcastHttpServer(findSettings()["podcast_port"]) self.httpd_thread = self.http_server.start() self.threads.append(self.httpd_thread) # Transcodeur - un seul pour tous les pipelines transcode_queue = Queue.Queue() for video in self.downloaded_files: if not video in self.transcoded_files: transcode_queue.put(video) self.transcode_thread = VideoTranscoder(transcode_queue, self.transcoded_files) self.transcode_thread.start() self.threads.append(self.transcode_thread) self.rss_feedserver = RssFeedHttpServer(findSettings()["podcast_port"] + 1) # Tous les pipelines for pipeline_name in self.pipeline_specs: logging.info("Pipeline " + pipeline_name) pipeline = PipelineFactory.createPipeline( pipeline_name, transcode_queue, self.pipeline_specs[pipeline_name]["input_spec"], self.pipeline_specs[pipeline_name]["podcast_spec"], self.downloaded_files, self.transcoded_files) pipeline_threads = pipeline.createThreads() self.threads += pipeline_threads # On cree enfin le thread sentinelle, qui va permettre de quitter # proprement en cas d'erreur. self.sentinel_thread = SentinelThread(self.threads)
def getRssFeedString(self, ip_address): """Renvoie le Flux RSS correspondant a une adresse IP du serveur donnee. L'adresse IP du serveur n'est pas fixe, car : - Le serveur peut en changer - Les differents clients peuvent acceder au serveur par une adresse differente. ex: acces par le reseau local et internet par NAT. Args: ip_address: L'adresse IP a preciser dans le flux, sous forme de string. """ self.addAllAvailableVideos() podcast_address = findSettings()["podcast_address_template"] % ip_address base_url = "%s:%d/" % (podcast_address, findSettings()["podcast_port"]) rss_feed = RSS2.RSS2( title=self.title, link=base_url + self.pipeline_name + ".xml", description=self.description, lastBuildDate=datetime.datetime.now()) for (video, size, change_time) in self.podcasted_videos: filename = video.local_transcoded_filename url = base_url + filename.split('/')[-1] if video.description is not None: description = video.description else: description = filename change_datetime = datetime.datetime.fromtimestamp(change_time) if video.name is not None: name = video.name else: name = filename video_enclosure = RSS2.Enclosure(url, size, "video/x-m4v") rss_item = RSS2.RSSItem( title=name, description=description, enclosure=video_enclosure, guid=RSS2.Guid(url), pubDate=change_datetime) rss_feed.items.append(rss_item) return rss_feed.to_xml()
def main(): option_parser = NiceOptionParser() option_parser.add_option("--config", help="Fichier de configuration", action="store", type="string", dest="config_file", default=findSettings()["config_file"]) option_parser.add_option("--verbose", help="Mode verbeux.", action="store_true", dest="verbose", default=False) (options, args) = option_parser.parse_args() if options.config_file is None or options.config_file == "": option_parser.print_usage() sys.exit(1) # On ne peut pas importer les modules avant car ils importent des # modules dependant de MyThread, dont la definition depend de la # valeur de with_gui, et qui est defini par initThreading(). from user_config import PipelineFactory, ConfigParser, iTunesConfig config_parser = ConfigParser(options.config_file) pipeline_specs = config_parser.parse() # On fait le chdir apres avoir lu les parametres logging.info("chdir " + findSettings()["output_dir"]) os.chdir(findSettings()["output_dir"]) # On fait le menage downloaded_files = SynchronizedSet('downloaded_videos') transcoded_files = SynchronizedSet('transcoded_videos') deleteOldVideos(downloaded_files, transcoded_files) # Si on est sur Darwin (OS X), on dit poliment bonjour if platform.system() == "Darwin": subprocess.call([findSettings()["growlnotify"], "-n", "PodcastCanalPlus", "-t", "PodcastCanalPlus", "-m", "Bonjour"]) from cli import CliInterface cli_interface = CliInterface(options) os._exit(cli_interface.run())
def downloadVideo(self, video): logging.info("Downloading Video: " + video.url) output_filename = findSettings()["output_dir"] + video.url.split('/')[-1] command = self.__COMMAND_TEMPLATE % {"url": video.url, "output_filename": output_filename} waiting_videos_count = self.download_queue.qsize() return_code = 1 retries = 0 while return_code != 0 and retries < 5: logging.info("Launching flvstreamer") logging.info(command) p = subprocess.Popen(command, shell=True, stderr=subprocess.PIPE) standard_error = utils.UnbufferedReadline(p.stderr) for line in standard_error.readlines(): # Si on doit mourrir, on le fait proprement if self.must_die_event.isSet(): # Popen.terminate() n'existe pas avant python 2.6. # On recupere donc le pid pour lui envoyer un signal pid = p.pid import signal os.kill(pid, signal.SIGTERM) p.wait() return False # On cherche si le nombre de videos en attente a varie. Si c'est le cas # on notifie l'interface graphique du changement. if self.download_queue.qsize() != waiting_videos_count: waiting_videos_count = self.download_queue.qsize() m = re.search("^\d+\.\d+ kB / \d+.\d+ sec \((.*)\%\)", line) if m: value = float(m.group(1)) self._status["percentage"] = value return_code = p.wait() retries += 1 if return_code != 0: logging.info("flvstreamer: code de retour != 0 (%d). essai %d" % (return_code, retries)) if return_code == 0: video.already_downloaded = True video.local_filename = output_filename return True else: logging.warning("Impossible de telecharger %s" % video) return False
def iTunesConfig(pipeline_specs): """Utilise AppleScript pour configurer iTunes de facon automatique. """ logging.info("Configuration d'iTunes.") script_template = """tell application "iTunes" subscribe "%s" end """ # Dans tous les cas, si on utilise le programme pour ajouter les podcasts # a iTunes, alors on est dans le cas ou le serveur est accessible par # localhost. base_address = "http://localhost:%d/" % findSettings()["podcast_port"] for pipeline_name in pipeline_specs: logging.info("\tPodcast: " + pipeline_name) pipeline_spec = pipeline_specs[pipeline_name] podcast_address = base_address + pipeline_spec["podcast_spec"]["filename"] script = script_template % podcast_address process = subprocess.Popen("osascript", shell=True, stdin=subprocess.PIPE) process.stdin.write(script) process.stdin.close() if process.wait() != 0: logging.warning("\tCode de retour != 0")