def __init__(self): self.navigateur = Navigateur() APIPrive.__init__(self) if API.INSTANCE != None: raise Exception("API est déjà instancier")
def __init__( self ): self.navigateur = Navigateur() APIPrive.__init__( self ) if API.INSTANCE != None: raise Exception("API est déjà instancier")
def __init__( self, url, # URL de la video proxy=None, # Proxy a utiliser proxySock=False, # Indique si le proxy est de type SOCK sousTitres=False, # Telechargement des sous-titres ? progressFnct=lambda x: None, # Callback pour la progression du telechargement stopDownloadEvent=threading.Event(), # Event pour arreter un telechargement outDir=".", # Repertoire de sortie de la video telechargee ): # Classe pour telecharger des fichiers self.navigateur = Navigateur(proxy, proxySock) # Infos video recuperees dans le XML self.id = None self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.m3u8URL = None self.drm = None self.chaine = None self.timeStamp = None self.codeProgramme = None # Recupere l'id de l'emission idEmission = self.getId(url) # Recupere la page d'infos de l'emission pageInfos = self.navigateur.getFichier(self.XML_DESCRIPTION.replace("_ID_EMISSION_", idEmission)) # Parse la page d'infos self.parseInfos(pageInfos) # Petit message en cas de DRM if self.drm == "oui": logger.warning("La vidéo posséde un DRM ; elle sera sans doute illisible") # Verification qu'un lien existe if self.m3u8URL is None and self.manifestURL is None and self.lienRTMP is None and self.lienMMS is None: raise PluzzDLException("Aucun lien vers la vidéo") # Le telechargement se fait de differente facon selon le type du lien disponible # Pour l'instant, seule la methode via les liens m3u8 fonctionne # Le code pour les autres liens reste quand meme en place pour etre utilise si les elements manquants sont trouves (clef HMAC par exemple) if self.m3u8URL is not None: # Nom du fichier nomFichier = self.getNomFichier(outDir, self.codeProgramme, self.timeStamp, "ts") # Downloader downloader = PluzzDLM3U8(self.m3u8URL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct) elif self.manifestURL is not None: # Nom du fichier nomFichier = self.getNomFichier(outDir, self.codeProgramme, self.timeStamp, "flv") # Downloader downloader = PluzzDLF4M(self.manifestURL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct) elif self.lienRTMP is not None: # Downloader downloader = PluzzDLRTMP(self.lienRTMP) elif self.lienMMS is not None: # Downloader downloader = PluzzDLMMS(self.lienMMS) # Recupere les sous titres si necessaire if sousTitres: self.telechargerSousTitres(idEmission, self.chaine, nomFichier) # Lance le téléchargement downloader.telecharger()
def __init__( self, url, useFragments = False, proxy = None, progressbar = False ): self.url = url self.useFragments = useFragments self.proxy = proxy self.progressbar = progressbar self.navigateur = Navigateur( self.proxy ) self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.m3u8URL = None self.drm = None # Recupere l'ID de l'emission self.getID() # Recupere la page d'infos de l'emission self.pageInfos = self.navigateur.getFichier( "http://www.pluzz.fr/appftv/webservices/video/getInfosOeuvre.php?mode=zeri&id-diffusion=%s" %( self.id ) ) # Parse la page d'infos self.parseInfos() # Petit message en cas de DRM if( self.drm == "oui" ): logger.warning( "La vidéo posséde un DRM ; elle sera sans doute illisible" )
class API(APIPrive): ## Instance utilisé par le programme INSTANCE = None ## Constructeur. # Ne pas utiliser. # @param self l'objet courant def __init__( self ): self.navigateur = Navigateur() APIPrive.__init__( self ) if API.INSTANCE != None: raise Exception("API est déjà instancier") ## Renvoie l'instance d'API # @return l'instance d'API @staticmethod def getInstance(): """Renvoie l'instance de l'API""" return API.INSTANCE ## Récupère une page web sur internet et remplace les caractères spéciaux (code HTML ou ISO). # @param self le plugin courant # @param url l'url de la page web # @return la page web sous forme d'une chaîne ou la chaîne vide en cas d'échec def getPage(self, url): return self.navigateur.getPage( url ) #~ #~ #Vérification de l'url #~ match = re.match(API.PATTERN_URL, url) #~ if match == None: #~ print "API.getPage(): url invalide." #~ return "" #~ #~ #Téléchargement et décompression si néscessaire #~ try: #~ connexion = httplib.HTTPConnection(match.group(1), timeout=APIPrive.HTTP_TIMEOUT) #~ #~ heads = {"Accept-Encoding":"deflate,gzip", #~ "Accept-Charset":"iso-8859-5, utf-8", #~ "User-Agent":choice(APIPrive.USER_AGENT)} #~ connexion.request("GET", match.group(2), headers=heads) #~ reponse = connexion.getresponse() #~ if reponse == None: #~ print "API.getPage(): erreur de téléchargement." #~ return "" #~ return self.reponseHttpToUTF8(reponse) #~ except Exception, ex: #~ print "API.getPage(): erreur de téléchargement.",ex #~ print_exc() #~ return "" def getPicture( self, url ): return self.navigateur.getPicture( url ) ## Récupère des pages webs sur internet et remplace les caractères spéciaux (code HTML ou ISO). Cette méthode reste connecté au serveur si il y a plusieurs page à y télécharger, elle est plus rapide que plusieurs appel à #getPage. # @param self le plugin courant # @param urls une liste d'url des pages à télécharger # @return un dictionnaire avec comme clé les urls et comme valeur les pages sous forme de chaîne def getPages(self, urls): reponses = self.navigateur.getPages( urls ) return reponses
def __init__(self): self.navigateur = Navigateur()
class UpdateManager(object): # Liste des sites qui disposent des mises a jour listeSites = [ "http://tvdownloader.googlecode.com/svn/branches/MAJ_Plugins" ] # Instance de la classe (singleton) instance = None ## Surcharge de la methode de construction standard (pour mettre en place le singleton) def __new__(self, *args, **kwargs): if (self.instance is None): self.instance = super(UpdateManager, self).__new__(self) return self.instance ## Constructeur def __init__(self): self.navigateur = Navigateur() ## Methode pour generer le fichier XML de description des plugins @staticmethod def creerXML(): # On ouvre le fichier XML try: fichierXML = open("versionsPlugins.xml", "wt") except: logger.error("impossible d'ouvrir le fichier XML en ecriture") return fichierXML.write('<?xml version="1.0" encoding="UTF-8"?>\n') fichierXML.write("<plugins>\n") # Pour chaque fichier de plugin for fichier in os.listdir("plugins"): # Tous les fichiers .py autre que __init__.py sont des plugins if (fichier[-3:] == ".py" and fichier.find("__init__.py") == -1): fichierCheminComplet = "plugins/%s" % (fichier) # Nom du fichier nom = fichier # Date de la derniere modification date = os.stat(fichierCheminComplet).st_mtime # Somme de controle SHA1 fichierPlugin = open(fichierCheminComplet, "rt") sha1 = hashlib.sha1(fichierPlugin.read()).hexdigest() fichierPlugin.close() # On ajoute l'element au fichier XML fichierXML.write( '\t<plugin nom="%s" dateModification="%s" sha1="%s"></plugin>\n' % (nom, date, sha1)) # On ferme le fichier XML fichierXML.write("</plugins>\n") fichierXML.close() ## Methode qui verifie si les plugins disposent d'une mise a jour # @param site Sites sur lesquel aller chercher la mise a jour # @return Liste des plugins a mettre a jour [ Nom du plugin, URL ou charger le plugin, SHA1 du plugin ] def verifierMiseAjour(self, site): listePluginsXML = [] # [ Nom, date derniere modification, SHA1 ] listePluginAMettreAJour = [ ] # [ Nom du plugin, URL ou charger le plugin, SHA1 du plugin ] handler = UpdateManagerHandler(listePluginsXML) # On recupere le fichier XML de description des plugins fichierXML = self.navigateur.getPage("%s/versionsPlugins.xml" % (site)) if (fichierXML == "" ): # Si on n'a rien recupere, on essaye un autre site logger.warn("aucune information disponible sur le site %s" % (site)) return [] # On vide la liste del listePluginsXML[:] # On parse le fichier XML try: xml.sax.parseString(fichierXML, handler) except: logger.error("impossible de parser le fichier XML du site %s" % (site)) return [] # Pour chaque plugin decrit dans le fichier XML for (nom, dateModification, sha1) in listePluginsXML: # On n'a pas une nouvelle version du plugin nouvelleVersion = False # Dans chacun des repertoires qui contiennent des plugins for rep in repPlugins: pluginCheminComplet = "%s/%s" % (rep, nom) # Si le plugin est present if (os.path.isfile(pluginCheminComplet)): # Si la version dont l'on dispose est moins recente que celle du site if (os.stat(pluginCheminComplet).st_mtime < dateModification): nouvelleVersion = True else: nouvelleVersion = False break # On peut arrete de chercher, on a deja le plus recent # S'il y a une nouvelle version if (nouvelleVersion): listePluginAMettreAJour.append( [nom, "%s/%s" % (site, nom), sha1]) # On a fini de lire les infos sur ce site, pas besoin de parcourir les autres return listePluginAMettreAJour # Methode pour installer la mise a jour d'un plugin # @param nomPlugin Nom du fichier du plugin a mettre a jour # @param urlPlugin URL ou charger le plugin # @param sha1 Somme de controle SHA1 du plugin # @return Si le plugin a ete correctement installe def mettreAJourPlugin(self, nomPlugin, urlPlugin, sha1): logger.info("mise a jour du plugin %s" % (nomPlugin)) # On telecharge le plugin codePlugin = self.navigateur.getPage(urlPlugin) if (codePlugin == ""): return False # On verifie la somme de controle du plugin if (hashlib.sha1(codePlugin).hexdigest() != sha1): logger.warn("somme de controle incorrecte pour le fichier %s" % (urlPlugin)) return False # On met en place le plugin fichier = open("%s/%s" % (repPlugins[0], nomPlugin), 'wt') fichier.write(codePlugin) fichier.close() return True
def __init__( self ): self.navigateur = Navigateur()
class UpdateManager( object ): # Liste des sites qui disposent des mises a jour listeSites = [ "http://tvdownloader.googlecode.com/svn/branches/MAJ_Plugins" ] # Instance de la classe (singleton) instance = None ## Surcharge de la methode de construction standard (pour mettre en place le singleton) def __new__( self, *args, **kwargs ): if( self.instance is None ): self.instance = super( UpdateManager, self ).__new__( self ) return self.instance ## Constructeur def __init__( self ): self.navigateur = Navigateur() ## Methode pour generer le fichier XML de description des plugins @staticmethod def creerXML(): # On ouvre le fichier XML try: fichierXML = open( "versionsPlugins.xml", "wt" ) except: logger.error( "impossible d'ouvrir le fichier XML en ecriture" ) return fichierXML.write( '<?xml version="1.0" encoding="UTF-8"?>\n' ) fichierXML.write( "<plugins>\n" ) # Pour chaque fichier de plugin for fichier in os.listdir( "plugins" ): # Tous les fichiers .py autre que __init__.py sont des plugins if( fichier [ -3 : ] == ".py" and fichier.find( "__init__.py" ) == -1 ): fichierCheminComplet = "plugins/%s" %( fichier ) # Nom du fichier nom = fichier # Date de la derniere modification date = os.stat( fichierCheminComplet ).st_mtime # Somme de controle SHA1 fichierPlugin = open( fichierCheminComplet, "rt" ) sha1 = hashlib.sha1( fichierPlugin.read() ).hexdigest() fichierPlugin.close() # On ajoute l'element au fichier XML fichierXML.write( '\t<plugin nom="%s" dateModification="%s" sha1="%s"></plugin>\n' %( nom, date, sha1 ) ) # On ferme le fichier XML fichierXML.write( "</plugins>\n" ) fichierXML.close() ## Methode qui verifie si les plugins disposent d'une mise a jour # @param site Sites sur lesquel aller chercher la mise a jour # @return Liste des plugins a mettre a jour [ Nom du plugin, URL ou charger le plugin, SHA1 du plugin ] def verifierMiseAjour( self, site ): listePluginsXML = [] # [ Nom, date derniere modification, SHA1 ] listePluginAMettreAJour = [] # [ Nom du plugin, URL ou charger le plugin, SHA1 du plugin ] handler = UpdateManagerHandler( listePluginsXML ) # On recupere le fichier XML de description des plugins fichierXML = self.navigateur.getPage( "%s/versionsPlugins.xml" %( site ) ) if( fichierXML == "" ): # Si on n'a rien recupere, on essaye un autre site logger.warn( "aucune information disponible sur le site %s" %( site ) ) return [] # On vide la liste del listePluginsXML[ : ] # On parse le fichier XML try: xml.sax.parseString( fichierXML, handler ) except: logger.error( "impossible de parser le fichier XML du site %s" %( site ) ) return [] # Pour chaque plugin decrit dans le fichier XML for( nom, dateModification, sha1 ) in listePluginsXML: # On n'a pas une nouvelle version du plugin nouvelleVersion = False # Dans chacun des repertoires qui contiennent des plugins for rep in repPlugins: pluginCheminComplet = "%s/%s" %( rep, nom ) # Si le plugin est present if( os.path.isfile( pluginCheminComplet ) ): # Si la version dont l'on dispose est moins recente que celle du site if( os.stat( pluginCheminComplet ).st_mtime < dateModification ): nouvelleVersion = True else: nouvelleVersion = False break # On peut arrete de chercher, on a deja le plus recent # S'il y a une nouvelle version if( nouvelleVersion ): listePluginAMettreAJour.append( [ nom, "%s/%s" %( site, nom ), sha1 ] ) # On a fini de lire les infos sur ce site, pas besoin de parcourir les autres return listePluginAMettreAJour # Methode pour installer la mise a jour d'un plugin # @param nomPlugin Nom du fichier du plugin a mettre a jour # @param urlPlugin URL ou charger le plugin # @param sha1 Somme de controle SHA1 du plugin # @return Si le plugin a ete correctement installe def mettreAJourPlugin( self, nomPlugin, urlPlugin, sha1 ): logger.info( "mise a jour du plugin %s" %( nomPlugin ) ) # On telecharge le plugin codePlugin = self.navigateur.getPage( urlPlugin ) if( codePlugin == "" ): return False # On verifie la somme de controle du plugin if( hashlib.sha1( codePlugin ).hexdigest() != sha1 ): logger.warn( "somme de controle incorrecte pour le fichier %s" %( urlPlugin ) ) return False # On met en place le plugin fichier = open( "%s/%s" %( repPlugins[ 0 ], nomPlugin ), 'wt' ) fichier.write( codePlugin ) fichier.close() return True
class PluzzDL( object ): def __init__( self, url, useFragments = False, proxy = None, resume = False, progressFnct = lambda x : None, stopDownloadEvent = threading.Event(), outDir = ".", manifest = False, playlist = False ): self.url = url self.useFragments = useFragments self.proxy = proxy self.resume = resume self.progressFnct = progressFnct self.stopDownloadEvent = stopDownloadEvent self.outDir = outDir self.navigateur = Navigateur( self.proxy ) self.historique = Historique() self.configuration = Configuration() self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.drm = None if playlist: self.mode = "playlist" self.ext = "ts" else: self.mode = "manifest" self.ext = "flv" self.hmacKey = self.configuration[ "hmac_key" ].decode( "hex" ) self.playerHash = self.configuration[ "player_hash" ] if( re.match( "http://www.pluzz.fr/[^\.]+?\.html", self.url ) ): # Recupere l'ID de l'emission self.getID() # Recupere la page d'infos de l'emission self.pageInfos = self.navigateur.getFichier( "http://www.pluzz.fr/appftv/webservices/video/getInfosOeuvre.php?mode=zeri&id-diffusion=%s" %( self.id ) ) # Parse la page d'infos self.parseInfos() # Petit message en cas de DRM if( self.drm == "oui" ): logger.warning( "La vidéo posséde un DRM ; elle sera sans doute illisible" ) # Lien MMS trouve if( self.lienMMS is not None ): logger.info( "Lien MMS : %s\nUtiliser par exemple mimms ou msdl pour la recuperer directement ou l'option -f de pluzzdl pour essayer de la charger via ses fragments" %( self.lienMMS ) ) # Lien RTMP trouve if( self.lienRTMP is not None ): logger.info( "Lien RTMP : %s\nUtiliser par exemple rtmpdump pour la recuperer directement ou l'option -f de pluzzdl pour essayer de la charger via ses fragments" %( self.lienRTMP ) ) # N'utilise pas les fragments si cela n'a pas ete demande et que des liens directs ont ete trouves if( ( ( self.lienMMS is not None ) or ( self.lienRTMP is not None ) ) and not self.useFragments ): sys.exit( 0 ) if self.mode == "manifest": # Lien du manifest non trouve if( self.manifestURL is None ): logger.critical( "Pas de lien vers le manifest" ) sys.exit( -1 ) if self.mode == "playlist": # Lien de la playlist M3U8 non trouve if( self.playlistM3U8 is None ): logger.critical( "Pas de lien vers la playlist" ) sys.exit( -1 ) self.nomFichier = os.path.join( self.outDir, "%s.%s" %( re.findall( "http://www.pluzz.fr/([^\.]+?)\.html", self.url )[ 0 ], self.ext ) ) else: if self.mode == "manifest": page = self.navigateur.getFichier( self.url ) try: self.manifestURL = re.findall( "(http://.+?manifest.f4m)", page )[ 0 ] except: logger.critical( "Pas de lien vers le manifest" ) sys.exit( -1 ) try: self.nomFichier = os.path.join( self.outDir, "%s.flv" %( self.url.split( "/" )[ -1 ] ) ) except: self.nomFichier = os.path.join( self.outDir, "video.flv" ) self.generateNomFichier() if self.mode == "manifest": self.getVideoViaManifest() if self.mode == "playlist": self.getVideoViaPlaylist() def getVideoViaManifest( self ): # Verifie si le lien du manifest contient la chaine "media-secure" if( self.manifestURL.find( "media-secure" ) != -1 ): logger.critical( "pluzzdl ne sait pas encore gérer ce type de vidéo..." ) sys.exit( 0 ) # Lien du manifest (apres le token) self.manifestURLToken = self.navigateur.getFichier( "http://hdfauth.francetv.fr/esi/urltokengen2.html?url=%s" %( self.manifestURL[ self.manifestURL.find( "/z/" ) : ] ) ) # Recupere le manifest self.manifest = self.navigateur.getFichier( self.manifestURLToken ) # Parse le manifest self.parseManifest() # Calcul les elements self.hdnea = self.manifestURLToken[ self.manifestURLToken.find( "hdnea" ) : ] self.pv20, self.hdntl = self.pv2.split( ";" ) self.pvtokenData = r"st=0000000000~exp=9999999999~acl=%2f%2a~data=" + self.pv20 + "!" + self.playerHash self.pvtoken = "pvtoken=%s~hmac=%s" %( urllib.quote( self.pvtokenData ), hmac.new( self.hmacKey, self.pvtokenData, hashlib.sha256 ).hexdigest() ) # # Creation de la video # self.premierFragment = 1 self.telechargementFini = False # S'il faut reprendre le telechargement if( self.resume ): video = self.historique.getVideo( self.urlFrag ) # Si la video est dans l'historique if( video is not None ): # Si la video existe sur le disque if( os.path.exists( self.nomFichier ) ): if( video.finie ): logger.info( "La vidéo a déjà été entièrement téléchargée" ) sys.exit( 0 ) else: self.ouvrirVideoExistante() self.premierFragment = video.fragments logger.info( "Reprise du téléchargement de la vidéo au fragment %d" %( video.fragments ) ) else: self.ouvrirNouvelleVideo() logger.info( "Impossible de reprendre le téléchargement de la vidéo, le fichier %s n'existe pas" %( self.nomFichier ) ) else: # Si la video n'est pas dans l'historique self.ouvrirNouvelleVideo() else: # S'il ne faut pas reprendre le telechargement self.ouvrirNouvelleVideo() # Calcul l'estimation du nombre de fragments self.nbFragMax = round( self.duree / 6 ) logger.debug( "Estimation du nombre de fragments : %d" %( self.nbFragMax ) ) # Ajout des fragments logger.info( "Début du téléchargement des fragments" ) try : i = self.premierFragment while( not self.stopDownloadEvent.isSet() ): frag = self.navigateur.getFichier( "%s%d?%s&%s&%s" %( self.urlFrag, i, self.pvtoken, self.hdntl, self.hdnea ) ) debut = self.debutVideo( i, frag ) self.fichierVideo.write( frag[ debut : ] ) # Affichage de la progression self.progressFnct( min( int( ( i / self.nbFragMax ) * 100 ), 100 ) ) i += 1 except urllib2.URLError, e : if( hasattr( e, 'code' ) ): if( e.code == 403 ): if( e.reason == "Forbidden" ): logger.info( "Le hash du player semble invalide ; calcul du nouveau hash" ) newPlayerHash = self.getPlayerHash() if( newPlayerHash != self.playerHash ): self.configuration[ "player_hash" ] = newPlayerHash self.configuration.writeConfig() logger.info( "Un nouveau hash a été trouvé ; essayez de relancer l'application" ) else: logger.critical( "Pas de nouveau hash disponible..." ) else: logger.critical( "Impossible de charger la vidéo" ) elif( e.code == 404 ): self.progressFnct( 100 ) self.telechargementFini = True logger.info( "Fin du téléchargement" ) self.convertVideo() except KeyboardInterrupt: logger.info( "Interruption clavier" )
def __init__(self, url, # URL de la video proxy = None, # Proxy à utiliser proxySock = False, # Proxy est-il de type SOCK? sousTitres = False, # Télécharger les sous-titres? progressFnct = lambda x: None, # Callback de progression stopDownloadEvent = threading.Event(), # → Arrêt outDir = "." # Répertoire de sortie ): # Classe pour télécharger des fichiers self.url = url self.navigateur = Navigateur(proxy, proxySock) self.pageHtml= None # Infos vidéo récupérées (dans la page, le xml, ou le json...) self.id = None self.lienMms = None self.lienRtmp = None self.lienCurl = None self.manifestUrl = None self.manifestUrlToken = None self.m3u8Url = None self.drm = None self.chaine = None self.timeStamp = None self.codeProgramme = None # Récupère la page html self.pageHtml = self.navigateur.getFichier(url) # Récupère les infos requises self.getInfos() # virtuelle # Petit message en cas de DRM if self.drm == "oui": logger.warning("La vidéo possède un DRM; elle sera sans doute illisible.") # Le téléchargement s'effectue en fonction du type de lien disponible if self.m3u8Url is not None: downloader = DlM3u8(self.m3u8Url, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.manifestUrl is not None: downloader = DlF4m(self.manifestUrl, self.manifestUrlToken, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.lienRtmp is not None: downloader = DlRtmp(self.lienRtmp, self.swfPlayerUrl, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.lienMms is not None: downloader = DlMms(self.lienMms, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.lienCurl: # Downloader downloader = DlCurl(self.lienCurl, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) else: # pas de lien connu détecté raise ReplayDlException("Aucun lien vers la vidéo!") # Récupère les sous-titres si possible if(sousTitres): self.telechargerSousTitres(self.id, self.chaine, nomFichier) # Lance le téléchargement downloader.telecharger() # Conversion finale downloader.convertir()
class ReplayDl(object): """ Classe principale pour lancer un téléchargement """ def __init__(self, url, # URL de la video proxy = None, # Proxy à utiliser proxySock = False, # Proxy est-il de type SOCK? sousTitres = False, # Télécharger les sous-titres? progressFnct = lambda x: None, # Callback de progression stopDownloadEvent = threading.Event(), # → Arrêt outDir = "." # Répertoire de sortie ): # Classe pour télécharger des fichiers self.url = url self.navigateur = Navigateur(proxy, proxySock) self.pageHtml= None # Infos vidéo récupérées (dans la page, le xml, ou le json...) self.id = None self.lienMms = None self.lienRtmp = None self.lienCurl = None self.manifestUrl = None self.manifestUrlToken = None self.m3u8Url = None self.drm = None self.chaine = None self.timeStamp = None self.codeProgramme = None # Récupère la page html self.pageHtml = self.navigateur.getFichier(url) # Récupère les infos requises self.getInfos() # virtuelle # Petit message en cas de DRM if self.drm == "oui": logger.warning("La vidéo possède un DRM; elle sera sans doute illisible.") # Le téléchargement s'effectue en fonction du type de lien disponible if self.m3u8Url is not None: downloader = DlM3u8(self.m3u8Url, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.manifestUrl is not None: downloader = DlF4m(self.manifestUrl, self.manifestUrlToken, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.lienRtmp is not None: downloader = DlRtmp(self.lienRtmp, self.swfPlayerUrl, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.lienMms is not None: downloader = DlMms(self.lienMms, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) elif self.lienCurl: # Downloader downloader = DlCurl(self.lienCurl, outDir, self.codeProgramme, self.timeStamp, self.navigateur, stopDownloadEvent, progressFnct) else: # pas de lien connu détecté raise ReplayDlException("Aucun lien vers la vidéo!") # Récupère les sous-titres si possible if(sousTitres): self.telechargerSousTitres(self.id, self.chaine, nomFichier) # Lance le téléchargement downloader.telecharger() # Conversion finale downloader.convertir() def getInfos(self): """ Cette méthode virtuelle doit être surchargée en fonction du site ciblé. Elle est chargée de récupérer toutes les informations nécessaires au téléchargement: - id de la vidéo - type de téléchargement - lien de téléchargement ... """ pass def telechargerSousTitres(self, idEmission, nomChaine, nomVideo): """ Récupère le fichier de sous titre de la vidéo. Virtuelle ← spécifique à Pluzz """ pass # À virer dans Downloader avec import datetime def getNomFichier(self, repertoire, codeProgramme, timeStamp, extension): """ Construit le nom du fichier de sortie """ nomFichier = os.path.join( repertoire, "%s_%s.%s" % ( codeProgramme, # enlever [ !:;?/<>] ? datetime.datetime.fromtimestamp(timeStamp).strftime( "%Y-%m-%d_%H-%M"), extension)) logger.debug(nomFichier) return nomFichier
"http://www.canalplus.fr/c-divertissement/pid1784-les-guignols-de-l-info.html?", "http://www.canalplus.fr/c-divertissement/pid1778-pepites-sur-le-net.html?", "http://www.canalplus.fr/c-divertissement/pid3279-reperages-l-emission.html?", "http://www.canalplus.fr/c-divertissement/pid2053-stephane-guillon.html?", "http://www.canalplus.fr/c-divertissement/pid3591-une-minute-avant.html?", "http://www.canalplus.fr/c-divertissement/pid3403-c-air-guitar-2010.html", "http://www.canalplus.fr/c-divertissement/pid3299-album-de-la-semaine.html?", "http://www.canalplus.fr/c-divertissement/pid3301-concert-prive.html?", "http://www.canalplus.fr/c-divertissement/pid3535-c-la-musicale.html", "http://www.canalplus.fr/c-divertissement/pid3308-la-radio-de-moustic.html?", "http://www.canalplus.fr/c-divertissement/pid3298-coming-next.html?", "http://www.canalplus.fr/c-divertissement/pid3522-u2-en-concert.html?", "http://www.canalplus.fr/c-divertissement/pid3303-le-live-du-grand-journal.html?" ] navigateur = Navigateur() api = API.getInstance() def testeMechanize(urls): reponses = {} for url in urls: reponses[url] = navigateur.getPage(url) def testeGetPage(urls): reponses = {} for url in urls: reponses[url] = api.getPage(url)
def __init__( self, url, # URL de la video proxy = None, # Proxy a utiliser proxySock = False, # Indique si le proxy est de type SOCK sousTitres = False, # Telechargement des sous-titres ? progressFnct = lambda x : None, # Callback pour la progression du telechargement stopDownloadEvent = threading.Event(), # Event pour arreter un telechargement outDir = "." # Repertoire de sortie de la video telechargee ): # Classe pour telecharger des fichiers self.navigateur = Navigateur( proxy, proxySock ) # Infos video recuperees dans le XML self.id = None self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.m3u8URL = None self.drm = None self.chaine = None self.timeStamp = None self.codeProgramme = None # Recupere l'id de l'emission idEmission = self.getId( url ) # Recupere la page d'infos de l'emission pageInfos = self.navigateur.getFichier( self.XML_DESCRIPTION.replace( "_ID_EMISSION_", idEmission ) ) # Parse la page d'infos self.parseInfos( pageInfos ) # Petit message en cas de DRM if( self.drm == "oui" ): logger.warning( "La vidéo posséde un DRM ; elle sera sans doute illisible" ) # Verification qu'un lien existe if( self.m3u8URL is None and self.manifestURL is None and self.lienRTMP is None and self.lienMMS is None ): raise PluzzDLException( "Aucun lien vers la vidéo" ) # Le telechargement se fait de differente facon selon le type du lien disponible # Pour l'instant, seule la methode via les liens m3u8 fonctionne # Le code pour les autres liens reste quand meme en place pour etre utilise si les elements manquants sont trouves (clef HMAC par exemple) if( self.m3u8URL is not None ): # Nom du fichier nomFichier = self.getNomFichier( outDir, self.codeProgramme, self.timeStamp, "ts" ) # Downloader downloader = PluzzDLM3U8( self.m3u8URL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct ) elif( self.manifestURL is not None ): # Nom du fichier nomFichier = self.getNomFichier( outDir, self.codeProgramme, self.timeStamp, "flv" ) # Downloader downloader = PluzzDLF4M( self.manifestURL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct ) elif( self.lienRTMP is not None ): # Downloader downloader = PluzzDLRTMP( self.lienRTMP ) elif( self.lienMMS is not None ): # Downloader downloader = PluzzDLMMS( self.lienMMS ) # Recupere les sous titres si necessaire if( sousTitres ): self.telechargerSousTitres( idEmission, self.chaine, nomFichier ) # Lance le téléchargement downloader.telecharger()
class PluzzDL( object ): """ Classe principale pour lancer un telechargement """ REGEX_ID = "http://info.francetelevisions.fr/\?id-video=([^\"]+)" XML_DESCRIPTION = "http://www.pluzz.fr/appftv/webservices/video/getInfosOeuvre.php?mode=zeri&id-diffusion=_ID_EMISSION_" URL_SMI = "http://www.pluzz.fr/appftv/webservices/video/getFichierSmi.php?smi=_CHAINE_/_ID_EMISSION_.smi&source=azad" M3U8_LINK = "http://medias2.francetv.fr/catchup-mobile/france-dom-tom/non-token/non-drm/m3u8/_FILE_NAME_.m3u8" REGEX_M3U8 = "/([0-9]{4}/S[0-9]{2}/J[0-9]{1}/[0-9]*-[0-9]{6,8})-" def __init__( self, url, # URL de la video proxy = None, # Proxy a utiliser proxySock = False, # Indique si le proxy est de type SOCK sousTitres = False, # Telechargement des sous-titres ? progressFnct = lambda x : None, # Callback pour la progression du telechargement stopDownloadEvent = threading.Event(), # Event pour arreter un telechargement outDir = "." # Repertoire de sortie de la video telechargee ): # Classe pour telecharger des fichiers self.navigateur = Navigateur( proxy, proxySock ) # Infos video recuperees dans le XML self.id = None self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.m3u8URL = None self.drm = None self.chaine = None self.timeStamp = None self.codeProgramme = None # Recupere l'id de l'emission idEmission = self.getId( url ) # Recupere la page d'infos de l'emission pageInfos = self.navigateur.getFichier( self.XML_DESCRIPTION.replace( "_ID_EMISSION_", idEmission ) ) # Parse la page d'infos self.parseInfos( pageInfos ) # Petit message en cas de DRM if( self.drm == "oui" ): logger.warning( "La vidéo posséde un DRM ; elle sera sans doute illisible" ) # Verification qu'un lien existe if( self.m3u8URL is None and self.manifestURL is None and self.lienRTMP is None and self.lienMMS is None ): raise PluzzDLException( "Aucun lien vers la vidéo" ) # Le telechargement se fait de differente facon selon le type du lien disponible # Pour l'instant, seule la methode via les liens m3u8 fonctionne # Le code pour les autres liens reste quand meme en place pour etre utilise si les elements manquants sont trouves (clef HMAC par exemple) if( self.m3u8URL is not None ): # Nom du fichier nomFichier = self.getNomFichier( outDir, self.codeProgramme, self.timeStamp, "ts" ) # Downloader downloader = PluzzDLM3U8( self.m3u8URL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct ) elif( self.manifestURL is not None ): # Nom du fichier nomFichier = self.getNomFichier( outDir, self.codeProgramme, self.timeStamp, "flv" ) # Downloader downloader = PluzzDLF4M( self.manifestURL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct ) elif( self.lienRTMP is not None ): # Downloader downloader = PluzzDLRTMP( self.lienRTMP ) elif( self.lienMMS is not None ): # Downloader downloader = PluzzDLMMS( self.lienMMS ) # Recupere les sous titres si necessaire if( sousTitres ): self.telechargerSousTitres( idEmission, self.chaine, nomFichier ) # Lance le téléchargement downloader.telecharger() def getId( self, url ): """ Recupere l'ID de la video a partir de son URL """ try : page = self.navigateur.getFichier( url ) idEmission = re.findall( self.REGEX_ID, page )[ 0 ] logger.debug( "ID de l'émission : %s" % ( idEmission ) ) return idEmission except : raise PluzzDLException( "Impossible de récupérer l'ID de l'émission" ) def parseInfos( self, pageInfos ): """ Parse le fichier de description XML d'une emission """ try : xml.sax.parseString( pageInfos, PluzzDLInfosHandler( self ) ) # Si le lien m3u8 n'existe pas, il faut essayer de creer celui de la plateforme mobile if( self.m3u8URL is None ): self.m3u8URL = self.M3U8_LINK.replace( "_FILE_NAME_", re.findall( self.REGEX_M3U8, pageInfos )[ 0 ] ) logger.debug( "URL m3u8 : %s" % ( self.m3u8URL ) ) logger.debug( "URL manifest : %s" % ( self.manifestURL ) ) logger.debug( "Lien RTMP : %s" % ( self.lienRTMP ) ) logger.debug( "Lien MMS : %s" % ( self.lienMMS ) ) logger.debug( "Utilisation de DRM : %s" % ( self.drm ) ) except : raise PluzzDLException( "Impossible de parser le fichier XML de l'émission" ) def getNomFichier( self, repertoire, codeProgramme, timeStamp, extension ): """ Construit le nom du fichier de sortie """ return os.path.join( repertoire, "%s_%s.%s" % ( codeProgramme, datetime.datetime.fromtimestamp( timeStamp ).strftime( "%Y-%m-%d_%H-%M" ), extension ) ) def telechargerSousTitres( self, idEmission, nomChaine, nomVideo ): """ Recupere le fichier de sous titre de la video """ urlSousTitres = self.URL_SMI.replace( "_CHAINE_", nomChaine.lower().replace( " ", "" ) ).replace( "_ID_EMISSION_", idEmission ) # Essaye de recuperer le sous titre try: sousTitresSmi = self.navigateur.getFichier( urlSousTitres ) except: logger.debug( "Sous titres indisponibles" ) return logger.debug( "Sous titres disponibles" ) # Enregistre le fichier de sous titres en smi try : ( nomFichierSansExtension, _ ) = os.path.splitext( nomVideo ) # Ecrit le fichier with open( "%s.smi" % ( nomFichierSansExtension ), "w" ) as f: f.write( sousTitresSmi ) except : raise PluzzDLException( "Impossible d'écrire dans le répertoire %s" % ( os.getcwd() ) ) logger.debug( "Fichier de sous titre smi enregistré" ) # Convertit le fichier de sous titres en srt try: with open( "%s.srt" % ( nomFichierSansExtension ), "w" ) as f: pageSoup = BeautifulSoup.BeautifulSoup( sousTitresSmi ) elmts = pageSoup.findAll( "sync" ) indice = 1 for ( elmtDebut, elmtFin ) in ( elmts[ i : i + 2 ] for i in range( 0, len( elmts ), 2 ) ): # Extrait le temps de debut et le texte tempsEnMs = int( elmtDebut[ "start" ] ) tempsDebutSrt = time.strftime( "%H:%M:%S,XXX", time.gmtime( int( tempsEnMs / 1000 ) ) ) tempsDebutSrt = tempsDebutSrt.replace( "XXX", str( tempsEnMs )[ -3 : ] ) lignes = elmtDebut.p.findAll( "span" ) texte = "\n".join( map( lambda x : x.contents[ 0 ].strip(), lignes ) ) # Extrait le temps de fin tempsEnMs = int( elmtFin[ "start" ] ) tempsFinSrt = time.strftime( "%H:%M:%S,XXX", time.gmtime( int( tempsEnMs / 1000 ) ) ) tempsFinSrt = tempsFinSrt.replace( "XXX", str( tempsEnMs )[ -3 : ] ) # Ecrit dans le fichier f.write( "%d\n" % ( indice ) ) f.write( "%s --> %s\n" % ( tempsDebutSrt, tempsFinSrt ) ) f.write( "%s\n\n" % ( texte.encode( "iso-8859-1" ) ) ) # Element suivant indice += 1 except: logger.error( "Impossible de convertir les sous titres en str" ) return logger.debug( "Fichier de sous titre srt enregistré" )
def __init__( self, url, useFragments = False, proxy = None, resume = False, progressFnct = lambda x : None, stopDownloadEvent = threading.Event(), outDir = ".", manifest = False, playlist = False ): self.url = url self.useFragments = useFragments self.proxy = proxy self.resume = resume self.progressFnct = progressFnct self.stopDownloadEvent = stopDownloadEvent self.outDir = outDir self.navigateur = Navigateur( self.proxy ) self.historique = Historique() self.configuration = Configuration() self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.drm = None if playlist: self.mode = "playlist" self.ext = "ts" else: self.mode = "manifest" self.ext = "flv" self.hmacKey = self.configuration[ "hmac_key" ].decode( "hex" ) self.playerHash = self.configuration[ "player_hash" ] if( re.match( "http://www.pluzz.fr/[^\.]+?\.html", self.url ) ): # Recupere l'ID de l'emission self.getID() # Recupere la page d'infos de l'emission self.pageInfos = self.navigateur.getFichier( "http://www.pluzz.fr/appftv/webservices/video/getInfosOeuvre.php?mode=zeri&id-diffusion=%s" %( self.id ) ) # Parse la page d'infos self.parseInfos() # Petit message en cas de DRM if( self.drm == "oui" ): logger.warning( "La vidéo posséde un DRM ; elle sera sans doute illisible" ) # Lien MMS trouve if( self.lienMMS is not None ): logger.info( "Lien MMS : %s\nUtiliser par exemple mimms ou msdl pour la recuperer directement ou l'option -f de pluzzdl pour essayer de la charger via ses fragments" %( self.lienMMS ) ) # Lien RTMP trouve if( self.lienRTMP is not None ): logger.info( "Lien RTMP : %s\nUtiliser par exemple rtmpdump pour la recuperer directement ou l'option -f de pluzzdl pour essayer de la charger via ses fragments" %( self.lienRTMP ) ) # N'utilise pas les fragments si cela n'a pas ete demande et que des liens directs ont ete trouves if( ( ( self.lienMMS is not None ) or ( self.lienRTMP is not None ) ) and not self.useFragments ): sys.exit( 0 ) if self.mode == "manifest": # Lien du manifest non trouve if( self.manifestURL is None ): logger.critical( "Pas de lien vers le manifest" ) sys.exit( -1 ) if self.mode == "playlist": # Lien de la playlist M3U8 non trouve if( self.playlistM3U8 is None ): logger.critical( "Pas de lien vers la playlist" ) sys.exit( -1 ) self.nomFichier = os.path.join( self.outDir, "%s.%s" %( re.findall( "http://www.pluzz.fr/([^\.]+?)\.html", self.url )[ 0 ], self.ext ) ) else: if self.mode == "manifest": page = self.navigateur.getFichier( self.url ) try: self.manifestURL = re.findall( "(http://.+?manifest.f4m)", page )[ 0 ] except: logger.critical( "Pas de lien vers le manifest" ) sys.exit( -1 ) try: self.nomFichier = os.path.join( self.outDir, "%s.flv" %( self.url.split( "/" )[ -1 ] ) ) except: self.nomFichier = os.path.join( self.outDir, "video.flv" ) self.generateNomFichier() if self.mode == "manifest": self.getVideoViaManifest() if self.mode == "playlist": self.getVideoViaPlaylist()
class API(APIPrive): ## Instance utilisé par le programme INSTANCE = None ## Constructeur. # Ne pas utiliser. # @param self l'objet courant def __init__(self): self.navigateur = Navigateur() APIPrive.__init__(self) if API.INSTANCE != None: raise Exception("API est déjà instancier") ## Renvoie l'instance d'API # @return l'instance d'API @staticmethod def getInstance(): """Renvoie l'instance de l'API""" return API.INSTANCE ## Récupère une page web sur internet et remplace les caractères spéciaux (code HTML ou ISO). # @param self le plugin courant # @param url l'url de la page web # @return la page web sous forme d'une chaîne ou la chaîne vide en cas d'échec def getPage(self, url): return self.navigateur.getPage(url) #~ #~ #Vérification de l'url #~ match = re.match(API.PATTERN_URL, url) #~ if match == None: #~ print "API.getPage(): url invalide." #~ return "" #~ #~ #Téléchargement et décompression si néscessaire #~ try: #~ connexion = httplib.HTTPConnection(match.group(1), timeout=APIPrive.HTTP_TIMEOUT) #~ #~ heads = {"Accept-Encoding":"deflate,gzip", #~ "Accept-Charset":"iso-8859-5, utf-8", #~ "User-Agent":choice(APIPrive.USER_AGENT)} #~ connexion.request("GET", match.group(2), headers=heads) #~ reponse = connexion.getresponse() #~ if reponse == None: #~ print "API.getPage(): erreur de téléchargement." #~ return "" #~ return self.reponseHttpToUTF8(reponse) #~ except Exception, ex: #~ print "API.getPage(): erreur de téléchargement.",ex #~ print_exc() #~ return "" def getPicture(self, url): return self.navigateur.getPicture(url) ## Récupère des pages webs sur internet et remplace les caractères spéciaux (code HTML ou ISO). Cette méthode reste connecté au serveur si il y a plusieurs page à y télécharger, elle est plus rapide que plusieurs appel à #getPage. # @param self le plugin courant # @param urls une liste d'url des pages à télécharger # @return un dictionnaire avec comme clé les urls et comme valeur les pages sous forme de chaîne def getPages(self, urls): reponses = self.navigateur.getPages(urls) return reponses
class PluzzDL( object ): def __init__( self, url, useFragments = False, proxy = None, progressbar = False ): self.url = url self.useFragments = useFragments self.proxy = proxy self.progressbar = progressbar self.navigateur = Navigateur( self.proxy ) self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.m3u8URL = None self.drm = None # Recupere l'ID de l'emission self.getID() # Recupere la page d'infos de l'emission self.pageInfos = self.navigateur.getFichier( "http://www.pluzz.fr/appftv/webservices/video/getInfosOeuvre.php?mode=zeri&id-diffusion=%s" %( self.id ) ) # Parse la page d'infos self.parseInfos() # Petit message en cas de DRM if( self.drm == "oui" ): logger.warning( "La vidéo posséde un DRM ; elle sera sans doute illisible" ) def getID( self ): try : page = self.navigateur.getFichier( self.url ) self.id = re.findall( r"http://info.francetelevisions.fr/\?id-video=([^\"]+)", page )[ 0 ] logger.debug( "ID de l'émission : %s" %( self.id ) ) except : logger.critical( "Impossible de récupérer l'ID de l'émission" ) sys.exit( -1 ) def parseInfos( self ): try : xml.sax.parseString( self.pageInfos, PluzzDLInfosHandler( self ) ) logger.debug( "Lien MMS : %s" %( self.lienMMS ) ) logger.debug( "Lien RTMP : %s" %( self.lienRTMP ) ) logger.debug( "URL manifest : %s" %( self.manifestURL ) ) logger.debug( "Utilisation de DRM : %s" %( self.drm ) ) except : logger.critical( "Impossible de parser le fichier XML de l'émission" ) sys.exit( -1 ) def parseManifest( self ): try : arbre = xml.etree.ElementTree.fromstring( self.manifest ) # Duree self.duree = float( arbre.find( "{http://ns.adobe.com/f4m/1.0}duration" ).text ) media = arbre.findall( "{http://ns.adobe.com/f4m/1.0}media" )[ -1 ] # Bitrate self.bitrate = int( media.attrib[ "bitrate" ] ) # URL des fragments urlbootstrap = media.attrib[ "url" ] self.urlFrag = "%s%sSeg1-Frag" %( self.manifestURLToken[ : self.manifestURLToken.find( "manifest.f4m" ) ], urlbootstrap ) # Header du fichier final self.flvHeader = base64.b64decode( media.find( "{http://ns.adobe.com/f4m/1.0}metadata" ).text ) except : logger.critical( "Impossible de parser le manifest" ) sys.exit( -1 )
class PluzzDL( object ): """ Classe principale pour lancer un telechargement """ REGEX_ID = "http://info.francetelevisions.fr/\?id-video=([^\"]+)" JSON_DESCRIPTION = "http://webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion=_ID_EMISSION_&catalogue=Pluzz" URL_SMI = "http://www.pluzz.fr/appftv/webservices/video/getFichierSmi.php?smi=_CHAINE_/_ID_EMISSION_.smi&source=azad" #M3U8_LINK = "http://medias2.francetv.fr/catchup-mobile/france-dom-tom/non-token/non-drm/m3u8/_FILE_NAME_.m3u8" M3U8_LINK = "http://medias2.francetv.fr/catchup-mobile/hls-ios-inf/i/streaming-adaptatif_france-dom-tom/_FILE_NAME_-,398,632,934,k.mp4.csmil/master.m3u8" REGEX_M3U8 = "/([0-9]{4}/S[0-9]{2}/J[0-9]{1}/[0-9]*-[0-9]{6,8})-" def __init__( self, url, # URL de la video proxy = None, # Proxy a utiliser proxySock = False, # Indique si le proxy est de type SOCK sousTitres = False, # Telechargement des sous-titres ? progressFnct = lambda x : None, # Callback pour la progression du telechargement stopDownloadEvent = threading.Event(), # Event pour arreter un telechargement outDir = "." # Repertoire de sortie de la video telechargee ): # Classe pour telecharger des fichiers self.navigateur = Navigateur( proxy, proxySock ) # Infos video recuperees dans le XML self.id = None self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.m3u8URL = None self.drm = None self.chaine = None self.timeStamp = None self.codeProgramme = None # Recupere l'id de l'emission idEmission = self.getId( url ) # Recupere la page d'infos de l'emission pageInfos = self.navigateur.getFichier( self.JSON_DESCRIPTION.replace( "_ID_EMISSION_", idEmission ) ) # Parse la page d'infos self.parseInfos( pageInfos ) # Petit message en cas de DRM if( self.drm == "oui" ): logger.warning( "La vidéo posséde un DRM ; elle sera sans doute illisible" ) # Verification qu'un lien existe if( self.m3u8URL is None and self.manifestURL is None and self.lienRTMP is None and self.lienMMS is None ): raise PluzzDLException( "Aucun lien vers la vidéo" ) # Le telechargement se fait de differente facon selon le type du lien disponible # Pour l'instant, seule la methode via les liens m3u8 fonctionne # Le code pour les autres liens reste quand meme en place pour etre utilise si les elements manquants sont trouves (clef HMAC par exemple) if( self.m3u8URL is not None ): # Nom du fichier nomFichier = self.getNomFichier( outDir, self.codeProgramme, self.timeStamp, "ts" ) # Downloader downloader = PluzzDLM3U8( self.m3u8URL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct ) elif( self.manifestURL is not None ): # Nom du fichier nomFichier = self.getNomFichier( outDir, self.codeProgramme, self.timeStamp, "flv" ) # Downloader downloader = PluzzDLF4M( self.manifestURL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct ) elif( self.lienRTMP is not None ): # Downloader downloader = PluzzDLRTMP( self.lienRTMP ) elif( self.lienMMS is not None ): # Downloader downloader = PluzzDLMMS( self.lienMMS ) # Recupere les sous titres si necessaire if( sousTitres ): self.telechargerSousTitres( idEmission, self.chaine, nomFichier ) # Lance le téléchargement downloader.telecharger() def getId( self, url ): """ Recupere l'ID de la video a partir de son URL """ try : page = self.navigateur.getFichier( url ) idEmission = re.findall( self.REGEX_ID, page )[ 0 ] logger.debug( "ID de l'émission : %s" % ( idEmission ) ) return idEmission except : raise PluzzDLException( "Impossible de récupérer l'ID de l'émission" ) def parseInfos( self, pageInfos ): """ Parse le fichier de description XML d'une emission """ try : #xml.sax.parseString( pageInfos, PluzzDLInfosHandler( self ) ) data = json.loads(pageInfos) self.codeProgramme = data["code_programme"] self.chaine = data["chaine"] self.timeStamp = float( data["diffusion"]["timestamp"] ) for v in data["videos"]: print v fmt = v["format"] if fmt == "smil-mp4": self.manifestURL = v["url"] if fmt == "m3u8-download": self.m3u8URL = v["url"] # TODO: check for other formats? self.drm = v["drm"] # Si le lien m3u8 n'existe pas, il faut essayer de creer celui de la plateforme mobile #XXX: #if( self.m3u8URL is None ): # self.m3u8URL = self.M3U8_LINK.replace( "_FILE_NAME_", re.findall( self.REGEX_M3U8, pageInfos )[ 0 ] ) logger.debug( "URL m3u8 : %s" % ( self.m3u8URL ) ) logger.debug( "URL manifest : %s" % ( self.manifestURL ) ) logger.debug( "Lien RTMP : %s" % ( self.lienRTMP ) ) logger.debug( "Lien MMS : %s" % ( self.lienMMS ) ) logger.debug( "Utilisation de DRM : %s" % ( self.drm ) ) except: raise PluzzDLException( "Impossible de parser le fichier JSON de l'émission" ) def getNomFichier( self, repertoire, codeProgramme, timeStamp, extension ): """ Construit le nom du fichier de sortie """ return os.path.join( repertoire, "%s_%s.%s" % ( codeProgramme, datetime.datetime.fromtimestamp( timeStamp ).strftime( "%Y-%m-%d_%H-%M" ), extension ) ) def telechargerSousTitres( self, idEmission, nomChaine, nomVideo ): """ Recupere le fichier de sous titre de la video """ urlSousTitres = self.URL_SMI.replace( "_CHAINE_", nomChaine.lower().replace( " ", "" ) ).replace( "_ID_EMISSION_", idEmission ) # Essaye de recuperer le sous titre try: sousTitresSmi = self.navigateur.getFichier( urlSousTitres ) except: logger.debug( "Sous titres indisponibles" ) return logger.debug( "Sous titres disponibles" ) # Enregistre le fichier de sous titres en smi try : ( nomFichierSansExtension, _ ) = os.path.splitext( nomVideo ) # Ecrit le fichier with open( "%s.smi" % ( nomFichierSansExtension ), "w" ) as f: f.write( sousTitresSmi ) except : raise PluzzDLException( "Impossible d'écrire dans le répertoire %s" % ( os.getcwd() ) ) logger.debug( "Fichier de sous titre smi enregistré" ) # Convertit le fichier de sous titres en srt try: with open( "%s.srt" % ( nomFichierSansExtension ), "w" ) as f: pageSoup = BeautifulSoup.BeautifulSoup( sousTitresSmi ) elmts = pageSoup.findAll( "sync" ) indice = 1 for ( elmtDebut, elmtFin ) in ( elmts[ i : i + 2 ] for i in range( 0, len( elmts ), 2 ) ): # Extrait le temps de debut et le texte tempsEnMs = int( elmtDebut[ "start" ] ) tempsDebutSrt = time.strftime( "%H:%M:%S,XXX", time.gmtime( int( tempsEnMs / 1000 ) ) ) tempsDebutSrt = tempsDebutSrt.replace( "XXX", str( tempsEnMs )[ -3 : ] ) lignes = elmtDebut.p.findAll( "span" ) texte = "\n".join( map( lambda x : x.contents[ 0 ].strip(), lignes ) ) # Extrait le temps de fin tempsEnMs = int( elmtFin[ "start" ] ) tempsFinSrt = time.strftime( "%H:%M:%S,XXX", time.gmtime( int( tempsEnMs / 1000 ) ) ) tempsFinSrt = tempsFinSrt.replace( "XXX", str( tempsEnMs )[ -3 : ] ) # Ecrit dans le fichier f.write( "%d\n" % ( indice ) ) f.write( "%s --> %s\n" % ( tempsDebutSrt, tempsFinSrt ) ) f.write( "%s\n\n" % ( texte.encode( "iso-8859-1" ) ) ) # Element suivant indice += 1 except: logger.error( "Impossible de convertir les sous titres en str" ) return logger.debug( "Fichier de sous titre srt enregistré" )
class PluzzDL(object): """ Classe principale pour lancer un telechargement """ DATA_MAIN_VIDEO= 'data-main-video="([0-9][a-z]-)*"' REGEX_ID = "http://info.francetelevisions.fr/\?id-video=([^\"]+)" XML_DESCRIPTION = "http://www.pluzz.fr/appftv/webservices/video/getInfosOeuvre.php?mode=zeri&id-diffusion=_ID_EMISSION_" JSON_DESCRIPTION = "http://webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion=_ID_EMISSION_&catalogue=Pluzz" URL_SMI = "http://www.pluzz.fr/appftv/webservices/video/getFichierSmi.php?smi=_CHAINE_/_ID_EMISSION_.smi&source=azad" M3U8_LINK = "http://medias2.francetv.fr/catchup-mobile/france-dom-tom/non-token/non-drm/m3u8/_FILE_NAME_.m3u8" REGEX_M3U8 = "/([0-9]{4}/S[0-9]{2}/J[0-9]{1}/[0-9]*-[0-9]{6,8})-" def __init__(self, url, # URL de la video proxy=None, # Proxy a utiliser proxySock=False, # Indique si le proxy est de type SOCK sousTitres=False, # Telechargement des sous-titres ? progressFnct=lambda x: None, # Callback pour la progression du telechargement stopDownloadEvent=threading.Event(), # Event pour arreter un telechargement outDir="." # Repertoire de sortie de la video telechargee ): # Classe pour telecharger des fichiers self.navigateur = Navigateur(proxy, proxySock) # Infos video recuperees dans le XML self.id = None self.lienMMS = None self.lienRTMP = None self.manifestURL = None self.m3u8URL = None self.drm = None self.chaine = None self.timeStamp = None self.codeProgramme = None # Recupere l'id de l'emission idEmission = self.getId(url) # Recupere la page d'infos de l'emission try: pageInfos = self.navigateur.getFichier(self.XML_DESCRIPTION.replace("_ID_EMISSION_", idEmission)) # Parse la page d'infos self.parseInfos(pageInfos) except: logger.debug("Problème avec le fichier XML, récupération du JSON") pageInfos = self.navigateur.getFichier(self.JSON_DESCRIPTION.replace("_ID_EMISSION_", idEmission)) self.parseInfosJSON(pageInfos) # Petit message en cas de DRM if (self.drm): logger.warning("La vidéo posséde un DRM ; elle sera sans doute illisible") # Verification qu'un lien existe if (self.m3u8URL is None and self.manifestURL is None and self.lienRTMP is None and self.lienMMS is None): raise PluzzDLException("Aucun lien vers la vidéo") # Le telechargement se fait de differente facon selon le type du lien disponible # Pour l'instant, seule la methode via les liens m3u8 fonctionne # Le code pour les autres liens reste quand meme en place pour etre utilise si les elements manquants sont trouves (clef HMAC par exemple) if (self.m3u8URL is not None): # Nom du fichier nomFichier = self.getNomFichier(outDir, self.codeProgramme, self.timeStamp, "ts") # Downloader downloader = PluzzDLM3U8(self.m3u8URL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct) elif (self.manifestURL is not None): # Nom du fichier nomFichier = self.getNomFichier(outDir, self.codeProgramme, self.timeStamp, "flv") # Downloader downloader = PluzzDLF4M(self.manifestURL, nomFichier, self.navigateur, stopDownloadEvent, progressFnct) elif (self.lienRTMP is not None): # Downloader downloader = PluzzDLRTMP(self.lienRTMP) elif (self.lienMMS is not None): # Downloader downloader = PluzzDLMMS(self.lienMMS) # Recupere les sous titres si necessaire if (sousTitres): self.telechargerSousTitres(idEmission, self.chaine, nomFichier) # Lance le téléchargement downloader.telecharger() def getId(self, url): """ Recupere l'ID de la video a partir de son URL """ # try : page = self.navigateur.getFichier(url) # idEmission = re.findall(self.REGEX_ID, page)[0] # LBR 10/05/2017 data_main_video_tag = page[page.find("data-main-video="):] data_id = data_main_video_tag[data_main_video_tag.find('"')+1:] id_emission = data_id[:data_id.find('"')] #data_main_video_tag = re.findall(self.DATA_MAIN_VIDEO, page)[0] print "Found data-main-video tag:", id_emission #idEmission = re.findall('"[0-9]*"', data_main_video_tag)[0] #idEmission = idEmission.strip('"') # idEmission = "157542198" logger.debug("ID de l'émission : %s" % (id_emission)) return id_emission # except : # raise PluzzDLException( "Impossible de récupérer l'ID de l'émission" ) def parseInfos(self, pageInfos): """ Parse le fichier de description XML d'une emission """ try: xml.sax.parseString(pageInfos, PluzzDLInfosHandler(self)) # Si le lien m3u8 n'existe pas, il faut essayer de creer celui de la plateforme mobile if (self.m3u8URL is None): logger.debug("m3u8URL file missing, we will try to guess it") if (self.manifestURL is not None): self.m3u8URL = self.manifestURL.replace("manifest.f4m", "index_2_av.m3u8") self.m3u8URL = self.m3u8URL.replace("/z/", "/i/") # self.m3u8URL = self.M3U8_LINK.replace( "_FILE_NAME_", re.findall( self.REGEX_M3U8, pageInfos )[ 0 ] ) logger.debug("URL m3u8 : %s" % (self.m3u8URL)) logger.debug("URL manifest : %s" % (self.manifestURL)) logger.debug("Lien RTMP : %s" % (self.lienRTMP)) logger.debug("Lien MMS : %s" % (self.lienMMS)) logger.debug("Utilisation de DRM : %s" % (self.drm)) except: raise PluzzDLException("Impossible de parser le fichier XML de l'émission") def parseInfosJSON(self, pageInfos): """ Parse le fichier de description JSON d'une emission """ try: data = json.loads(pageInfos) self.lienRTMP = None self.lienMMS = None self.timeStamp = data['diffusion']['timestamp'] self.codeProgramme = data['code_programme'] for v in data['videos']: if v['format'] == 'm3u8-download': self.m3u8URL = v['url'] self.drm = v['drm'] elif v['format'] == 'smil-mp4': self.manifestURL = v['url'] logger.debug("URL m3u8 : %s" % (self.m3u8URL)) logger.debug("URL manifest : %s" % (self.manifestURL)) logger.debug("Lien RTMP : %s" % (self.lienRTMP)) logger.debug("Lien MMS : %s" % (self.lienMMS)) logger.debug("Utilisation de DRM : %s" % (self.drm)) except: raise PluzzDLException("Impossible de parser le fichier JSON de l'émission") def getNomFichier(self, repertoire, codeProgramme, timeStamp, extension): """ Construit le nom du fichier de sortie """ return os.path.join(repertoire, "%s-%s.%s" % ( datetime.datetime.fromtimestamp(timeStamp).strftime("%Y%m%d"), codeProgramme, extension)) def telechargerSousTitres(self, idEmission, nomChaine, nomVideo): """ Recupere le fichier de sous titre de la video """ urlSousTitres = self.URL_SMI.replace("_CHAINE_", nomChaine.lower().replace(" ", "")).replace("_ID_EMISSION_", idEmission) # Essaye de recuperer le sous titre try: sousTitresSmi = self.navigateur.getFichier(urlSousTitres) except: logger.debug("Sous titres indisponibles") return logger.debug("Sous titres disponibles") # Enregistre le fichier de sous titres en smi try: (nomFichierSansExtension, _) = os.path.splitext(nomVideo) # Ecrit le fichier with open("%s.smi" % (nomFichierSansExtension), "w") as f: f.write(sousTitresSmi) except: raise PluzzDLException("Impossible d'écrire dans le répertoire %s" % (os.getcwd())) logger.debug("Fichier de sous titre smi enregistré") # Convertit le fichier de sous titres en srt try: with open("%s.srt" % (nomFichierSansExtension), "w") as f: pageSoup = BeautifulSoup.BeautifulSoup(sousTitresSmi) elmts = pageSoup.findAll("sync") indice = 1 for (elmtDebut, elmtFin) in (elmts[i: i + 2] for i in range(0, len(elmts), 2)): # Extrait le temps de debut et le texte tempsEnMs = int(elmtDebut["start"]) tempsDebutSrt = time.strftime("%H:%M:%S,XXX", time.gmtime(int(tempsEnMs / 1000))) tempsDebutSrt = tempsDebutSrt.replace("XXX", str(tempsEnMs)[-3:]) lignes = elmtDebut.p.findAll("span") texte = "\n".join(map(lambda x: x.contents[0].strip(), lignes)) # Extrait le temps de fin tempsEnMs = int(elmtFin["start"]) tempsFinSrt = time.strftime("%H:%M:%S,XXX", time.gmtime(int(tempsEnMs / 1000))) tempsFinSrt = tempsFinSrt.replace("XXX", str(tempsEnMs)[-3:]) # Ecrit dans le fichier f.write("%d\n" % (indice)) f.write("%s --> %s\n" % (tempsDebutSrt, tempsFinSrt)) f.write("%s\n\n" % (texte.encode("iso-8859-1"))) # Element suivant indice += 1 except: logger.error("Impossible de convertir les sous titres en str") return logger.debug("Fichier de sous titre srt enregistré")