Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1751 Le 06/05/2012, à 15:42

Babar21

Re : TVDownloader: télécharger les médias du net !

J'ai récupéré l'archive et je l'ai décompressée, ça roule mais c'est moins propre. L'heure de la mise à jour d'Ubuntu approche on dirait smile

Hors ligne

#1752 Le 06/05/2012, à 16:21

chaoswizard

Re : TVDownloader: télécharger les médias du net !

Il fallait directement essayer d'installer le paquet deb et voir si ça fonctionnait !


Ubuntu ==> Debian ==> Archlinux

Hors ligne

#1753 Le 06/05/2012, à 23:40

ubuntuzer

Re : TVDownloader: télécharger les médias du net !

@chaoswizard :
encore merci pour ton travail de maintien, cela profite à beaucoup d'internautes libres :-)
Concernant les liens RTMP et MMS, il m'est arriver de les utiliser lorsque le DL ne fonctionnait pas (ex : le dessin animé préféré de mon fils(RTMP) et "Un village Français" (MMS))
Suis-je le seul ?

Hors ligne

#1754 Le 07/05/2012, à 17:07

chaoswizard

Re : TVDownloader: télécharger les médias du net !

Bon, ben tu fais bien de le dire, j'allais les virer !


Ubuntu ==> Debian ==> Archlinux

Hors ligne

#1755 Le 08/05/2012, à 12:59

Babar21

Re : TVDownloader: télécharger les médias du net !

Bonjour.

J'ai des téléchargements incomplets sur les vidéos suivantes :

http://www.pluzz.fr/elysee-2012-la-vraie-campagne.html

Les 16 premières minutes dispos, puis le film se coupe. Fichier déclaré à 100% de 689 Mo.

http://www.pluzz.fr/francois-hollande-- … 22h30.html

Le début et la fin sont disponibles, mais grosse coupure au milieu (pas loin d'une heure). Déclaré à 100%, 468 Mo.

Hors ligne

#1756 Le 08/05/2012, à 23:10

k3c

Re : TVDownloader: télécharger les médias du net !

Bonjour

Je confirme pour la première vidéo, un peu après 16 minutes, on passe à la fin. Fichier de 689 Mo moi aussi.

vlc annonce 1h48, comme mediainfo. Si des experts en vidéo voient un truc qui cloche dans ce qui suit, ils sont les bienvenus

mediainfo elysee-2012-la-vraie-campagne.flv
General
Complete name                            : elysee-2012-la-vraie-campagne.flv
Format                                   : Flash Video
File size                                : 657 MiB
Duration                                 : 1h 48mn
Overall bit rate                         : 849 Kbps

Video
Format                                   : AVC
Format/Info                              : Advanced Video Codec
Format profile                           : Main@L3.1
Format settings, CABAC                   : Yes
Format settings, ReFrames                : 4 frames
Codec ID                                 : 7
Duration                                 : 1h 48mn
Bit rate                                 : 768 Kbps
Width                                    : 704 pixels
Height                                   : 400 pixels
Display aspect ratio                     : 16:9
Frame rate mode                          : Constant
Frame rate                               : 25.000 fps
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Bits/(Pixel*Frame)                       : 0.109
Stream size                              : 594 MiB (90%)
Writing library                          : x264 core 120 r2164 da19765
Encoding settings                        : cabac=1 / ref=1 / deblock=1:0:0 / analyse=0x1:0 / me=dia / subme=2 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=0 / me_range=16 / chroma_me=1 / trellis=0 / 8x8dct=0 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=0 / threads=12 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=75 / keyint_min=7 / scenecut=40 / intra_refresh=0 / rc_lookahead=40 / rc=abr / mbtree=1 / bitrate=768 / ratetol=1.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / ip_ratio=1.40 / aq=1:1.00

Audio
Format                                   : AAC
Format/Info                              : Advanced Audio Codec
Format profile                           : LC
Codec ID                                 : 10
Duration                                 : 1h 48mn
Bit rate                                 : 64.0 Kbps
Channel(s)                               : 2 channels
Channel positions                        : Front: L R
Sampling rate                            : 48.0 KHz
Compression mode                         : Lossy
Stream size                              : 49.5 MiB (8%)


gg@gg-SATELLITE-L755:~$ 

Pour la deuxième vidéo, on passe de 16 minutes 23 s à 1h 05 minutes directement.

J'ai mis dans un fichier le log du téléchargement, cela indique nombre approximatif de framents 610, cela en montre finalement 732, sans rupture (on ne passe pas de 223 à 280, par exemple), de 1 à 732.

mediainfo francois-hollande---comment-devenir-2012-05-07-22h30.flv
General
Complete name                            : francois-hollande---comment-devenir-2012-05-07-22h30.flv
Format                                   : Flash Video
File size                                : 447 MiB
Duration                                 : 1h 13mn
Overall bit rate                         : 855 Kbps

Video
Format                                   : AVC
Format/Info                              : Advanced Video Codec
Format profile                           : Main@L3.1
Format settings, CABAC                   : Yes
Format settings, ReFrames                : 4 frames
Codec ID                                 : 7
Duration                                 : 1h 13mn
Bit rate                                 : 768 Kbps
Width                                    : 704 pixels
Height                                   : 400 pixels
Display aspect ratio                     : 16:9
Frame rate mode                          : Constant
Frame rate                               : 25.000 fps
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Bits/(Pixel*Frame)                       : 0.109
Stream size                              : 402 MiB (90%)
Writing library                          : x264 core 120 r2164 da19765
Encoding settings                        : cabac=1 / ref=1 / deblock=1:0:0 / analyse=0x1:0 / me=dia / subme=2 / psy=1 / psy_rd=1.00:0.00 / mixed_ref=0 / me_range=16 / chroma_me=1 / trellis=0 / 8x8dct=0 / cqm=0 / deadzone=21,11 / fast_pskip=1 / chroma_qp_offset=0 / threads=12 / sliced_threads=0 / nr=0 / decimate=1 / interlaced=0 / bluray_compat=0 / constrained_intra=0 / bframes=3 / b_pyramid=2 / b_adapt=1 / b_bias=0 / direct=1 / weightb=1 / open_gop=0 / weightp=2 / keyint=75 / keyint_min=7 / scenecut=40 / intra_refresh=0 / rc_lookahead=40 / rc=abr / mbtree=1 / bitrate=768 / ratetol=1.0 / qcomp=0.60 / qpmin=0 / qpmax=69 / qpstep=4 / ip_ratio=1.40 / aq=1:1.00

Audio
Format                                   : AAC
Format/Info                              : Advanced Audio Codec
Format profile                           : LC
Codec ID                                 : 10
Duration                                 : 1h 13mn
Bit rate                                 : 64.0 Kbps
Channel(s)                               : 2 channels
Channel positions                        : Front: L R
Sampling rate                            : 48.0 KHz
Compression mode                         : Lossy
Stream size                              : 33.5 MiB (7%)

Dernière modification par k3c (Le 08/05/2012, à 23:19)


Debian 12 sur Thinkpad reconditionné

Hors ligne

#1757 Le 08/05/2012, à 23:43

bibichouchou

Re : TVDownloader: télécharger les médias du net !

salut à tous,

je viens d'essayer et je confirme le problème pour la deuxième vidéo, mêmes manifestations que celles décrites par k3c. je télécharge 731 fragments en tout.
je propose un nouveau motif pour le découpage-collage des fragments.

au lien de

frag.find("\x0f\x09")+1

j'utilise

frag.find("\x00\x0f\x09")+2

c'est du bricolage, mais bon...
cette fois, j'ai la vidéo complète sur notre nouveau président !!! wink

Edit : cela marche aussi pour le beau reportage de Moati.
A voir ce que cela donne pour d'autres vidéos...

Dernière modification par bibichouchou (Le 09/05/2012, à 00:29)

Hors ligne

#1758 Le 09/05/2012, à 00:38

k3c

Re : TVDownloader: télécharger les médias du net !

Bravo bibichouchou, j'ai donc appliqué 2 fois ta modif dans /usr/share/pluzzdl/Pluzzdl.py

et le fichier francois-hollande-comment-devenir... est lisible de partout.

Edit : le fichier elysee-2012-la-vraie-campagne.flv est lisible de partout, lui aussi.

Dernière modification par k3c (Le 09/05/2012, à 00:56)


Debian 12 sur Thinkpad reconditionné

Hors ligne

#1759 Le 09/05/2012, à 01:22

bibichouchou

Re : TVDownloader: télécharger les médias du net !

@k3c
est-ce que chez toi la vidéo de moati a quelques défauts à la toute fin (pdt le générique et un peu avant) ?
c'est juste pour comparer. je me suis aperçu de ça en naviguant dans la vidéo...

Hors ligne

#1760 Le 09/05/2012, à 01:56

k3c

Re : TVDownloader: télécharger les médias du net !

@ bibichouchou

oui, dans le film elysee.., entre 1h47mn18s et 1h48mns13s, il y a plusieurs défauts.
Edit : si je regarde sur Pluzz, je vois les mêmes défauts. Donc pas de pb je pense.

Je n'ai rien vu à la fin de l'autre vidéo.

Dernière modification par k3c (Le 09/05/2012, à 02:07)


Debian 12 sur Thinkpad reconditionné

Hors ligne

#1761 Le 09/05/2012, à 08:48

Babar21

Re : TVDownloader: télécharger les médias du net !

Merci à vous deux, j'ai pas bien tout compris à ce qu'il fallait modifier et où, mais je vais tenter le coup smile

Hors ligne

#1762 Le 09/05/2012, à 10:02

bibichouchou

Re : TVDownloader: télécharger les médias du net !

@k3c
oui, j'ai aussi noté que les défauts sont présents dans la vidéo en ligne. donc il n'y a rien à faire.

@Babar21

il faut modifier le fichier Pluzzdl.py. il se trouve dans le dossier src/ de l'archive pluzzdl-0.8.1.tar.gz ou alors dans /usr/share/pluzzdl/. ça dépend de comment tu l'as installé.
et tu remplaces

frag.find("\x0f\x09")+1

par

frag.find("\x00\x0f\x09")+2

avec ton éditeur de texte préféré.
bonne journée !

ps : avant de propager la modif dans le paquet source, il faudrait tester avec d'autres vidéos pour voir si c'est ok...

Dernière modification par bibichouchou (Le 09/05/2012, à 10:05)

Hors ligne

#1763 Le 09/05/2012, à 10:18

Babar21

Re : TVDownloader: télécharger les médias du net !

Bon, j'ai dû me tromper quelque part, ça bloque toujours à 16 minutes.
Je vais attendre une mise à jour ou un correctif "officiel".

Hors ligne

#1764 Le 09/05/2012, à 13:02

k3c

Re : TVDownloader: télécharger les médias du net !

@ Babar21

Voici mon fichier /usr/share/pluzzdl/Pluzdl.py

#!/usr/bin/env python
# -*- coding:Utf-8 -*-

# Notes :
#    -> Filtre Wireshark : 
#          http.host contains "ftvodhdsecz" or http.host contains "francetv" or http.host contains "pluzz"
#    -> 

#
# Modules
#

import base64
import binascii
import hashlib
import hmac
import os
import re
import sys
import urllib
import urllib2
import xml.etree.ElementTree
import xml.sax

from Historique import Historique, Video
from Navigateur import Navigateur

import logging
logger = logging.getLogger( "pluzzdl" )

#
# Classes
#

class PluzzDL( object ):
    
    def __init__( self, url, useFragments = False, proxy = None, progressbar = False, resume = False ):
        self.url              = url
        self.useFragments     = useFragments
        self.proxy            = proxy
        self.progressbar      = progressbar
        self.resume           = resume
        self.navigateur       = Navigateur( self.proxy )
        self.historique       = Historique()
        
        self.lienMMS          = None
        self.lienRTMP         = None
        self.manifestURL      = None
        self.drm              = None
        
        self.key        = r"bd938d5ee6d9f42016f9c56577b6fdcf415fe4b184932b785ab32bcadc9bb592".decode( "hex" )
        self.pvtokenKey = r"3fPHzcjMxiTmRj5AdV0bhzgjjSjk2PqUMBzFxgPEPF4="
        
        # 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 )
        # Lien du manifest non trouve
        if( self.manifestURL is None ):
            logger.critical( "Pas de lien vers le manifest" )
            sys.exit( -1 )
        # 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.pvtokenKey
        self.pvtoken = "pvtoken=%s~hmac=%s" %( urllib.quote( self.pvtokenData ), hmac.new( self.key, self.pvtokenData, hashlib.sha256 ).hexdigest() )
        
        #
        # Creation de la video
        #
        self.nomFichier      = "%s.flv" %( re.findall( "http://www.pluzz.fr/([^\.]+?)\.html", self.url )[ 0 ] )
        self.premierFragment = 1
        
        # 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 * self.bitrate ) / 6040.0, 0 )
        logger.debug( "Estimation du nombre de fragments : %d" %( self.nbFragMax ) )
        if( self.progressbar and self.nbFragMax != 0 ):
            self.progression = Progression( self.nbFragMax, self.premierFragment )
        else:
            self.progression = ProgressionVide( self.nbFragMax, self.premierFragment )
        
        # Ajout des fragments
        logger.info( "Début du téléchargement des fragments" )
        try :
            i = self.premierFragment
            frag = self.navigateur.getFichier( "%s%d?%s&%s&%s" %( self.urlFrag, i, self.pvtoken, self.hdntl, self.hdnea ) )
            if( i == 1 ):
                self.fichierVideo.write( frag[ frag.find( "mdat" ) + 4 : ] )
            else:
                self.fichierVideo.write( frag[ frag.find("\x00\x0f\x09") + 2 : ] )
                              #  self.fichierVideo.write( frag[ frag.find( "\x0f\x09" ) + 1 : ] )

            # Affichage de la progression
            self.progression.afficher()
            for i in xrange( self.premierFragment + 1, 99999 ):
                frag = self.navigateur.getFichier( "%s%d?%s&%s&%s" %( self.urlFrag, i, self.pvtoken, self.hdntl, self.hdnea ) )
                self.fichierVideo.write( frag[ frag.find( "\x00\x0f\x09") + 2 : ] )
                # self.fichierVideo.write( frag[ frag.find( "mdat" ) + 79 : ] )
                # Affichage de la progression
                self.progression.afficher()
        except urllib2.URLError, e :
            if( hasattr( e, 'code' ) ):
                if( e.code == 403 ):
                    self.historique.ajouter( Video( lien = self.urlFrag, fragments = i, finie = False ) )
                    logger.critical( "Impossible de charger la vidéo" )
                elif( e.code == 404 ):
                    self.progression.afficherFin()
                    self.historique.ajouter( Video( lien = self.urlFrag, fragments = i, finie = True ) )
                    logger.info( "Fin du téléchargement" )
        except :
            self.historique.ajouter( Video( lien = self.urlFrag, fragments = i, finie = False ) )
        else :
            # Fermeture du fichier
            self.fichierVideo.close()
        
    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 )
            self.pv2       = arbre.find( "{http://ns.adobe.com/f4m/1.0}pv-2.0" ).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 )

    def ouvrirNouvelleVideo( self ):
        try :
            # Ouverture du fichier
            self.fichierVideo = open( self.nomFichier, "wb" )
        except :
            logger.critical( "Impossible d'écrire dans le répertoire %s" %( os.getcwd() ) )
            sys.exit( -1 )
        # Ajout de l'en-tête FLV
        self.fichierVideo.write( binascii.a2b_hex( "464c56010500000009000000001200010c00000000000000" ) )
        # Ajout de l'header du fichier
        self.fichierVideo.write( self.flvHeader )
        self.fichierVideo.write( binascii.a2b_hex( "00000000" ) ) # Padding pour avoir des blocs de 8
        
    def ouvrirVideoExistante( self ):
        try :
            # Ouverture du fichier
            self.fichierVideo = open( self.nomFichier, "a+b" )
        except :
            logger.critical( "Impossible d'écrire dans le répertoire %s" %( os.getcwd() ) )
            sys.exit( -1 )
        
class PluzzDLInfosHandler( xml.sax.handler.ContentHandler ):
    
    def __init__( self, pluzzdl ):
        self.pluzzdl = pluzzdl
        
        self.isUrl = False
        self.isDRM = False
        
    def startElement( self, name, attrs ):
        if( name == "url" ):
            self.isUrl = True
        elif( name == "drm" ):
            self.isDRM = True
    
    def characters( self, data ):
        if( self.isUrl ):
            if( data[ : 3 ] == "mms" ):
                self.pluzzdl.lienMMS = data
            elif( data[ : 4 ] == "rtmp" ):
                self.pluzzdl.lienRTMP = data
            elif( data[ -3 : ] == "f4m" ):
                self.pluzzdl.manifestURL = data
        elif( self.isDRM ):
            self.pluzzdl.drm = data
            
    def endElement( self, name ):
        if( name == "url" ):
            self.isUrl = False
        elif( name == "drm" ):
            self.isDRM = False

class ProgressionVide( object ):
    
    def __init__( self, nbMax, indice = 1 ):
        self.nbMax  = nbMax
        self.indice = indice
        self.old    = 0
        self.new    = 0
    
    def afficher( self ):
        pass
        
    def afficherFin( self ):
        pass
    
class Progression( ProgressionVide ):
    
    def __init__( self, nbMax, indice ):
        ProgressionVide.__init__( self, nbMax, indice )
        
    def afficher( self ):
        self.new = min( int( ( self.indice / self.nbMax ) * 100 ), 100 )
        if( self.new != self.old ):
            logger.info( "Avancement : %3d %%" %( self.new ) )
            self.old = self.new
        self.indice += 1
    
    def afficherFin( self ):
        if( self.old < 100 ):
            logger.info( "Avancement : %3d %%" %( 100 ) )

Tu peux donc le copier.
Il y a 2 modifs à faire, j'ai laissé en commentaire l'ancienne ligne pour la première modif, la deuxième modif est 5 ou 6 lignes plus loin.


Debian 12 sur Thinkpad reconditionné

Hors ligne

#1765 Le 09/05/2012, à 14:24

thom83

Re : TVDownloader: télécharger les médias du net !

Le fait de modifier le fichier Pluzdl.py ne change rien à son pendant Pluzdl.pyc. Cela n'a-t-il pas une incidence sur le résultat final quand on utilise pluzzdl ?

Hors ligne

#1766 Le 09/05/2012, à 14:45

miztadux

Re : TVDownloader: télécharger les médias du net !

Salut,

Tout d'abord merci pour cet outil et le travail fourni par tous :)


Concernant le "collage des morceaux" j'ai un bout de code alternatif pour sauter le début des fragments.
Il devrait permettre d'eviter certains bugs rencontrés actuellement.

L'idée est, au lieu de se baser sur le "\x0f\x09", de prendre en compte un minimum du format flv pour être sûr de ce que l'on saute.
Ci-dessous le fichier PluzzDL.py v0.8.1 avec les modifs necessaires:

#!/usr/bin/env python
# -*- coding:Utf-8 -*-

# Notes :
#    -> Filtre Wireshark : 
#          http.host contains "ftvodhdsecz" or http.host contains "francetv" or http.host contains "pluzz"
#    -> 

#
# Modules
#

import struct
import base64
import binascii
import hashlib
import hmac
import os
import re
import sys
import urllib
import urllib2
import xml.etree.ElementTree
import xml.sax

from Historique import Historique, Video
from Navigateur import Navigateur

import logging
logger = logging.getLogger( "pluzzdl" )

#
# Classes
#

class PluzzDL( object ):
    
    def __init__( self, url, useFragments = False, proxy = None, progressbar = False, resume = False ):
        self.url              = url
        self.useFragments     = useFragments
        self.proxy            = proxy
        self.progressbar      = progressbar
        self.resume           = resume
        self.navigateur       = Navigateur( self.proxy )
        self.historique       = Historique()
        
        self.lienMMS          = None
        self.lienRTMP         = None
        self.manifestURL      = None
        self.drm              = None
        
        self.key        = r"bd938d5ee6d9f42016f9c56577b6fdcf415fe4b184932b785ab32bcadc9bb592".decode( "hex" )
        self.pvtokenKey = r"3fPHzcjMxiTmRj5AdV0bhzgjjSjk2PqUMBzFxgPEPF4="
        
        # 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 )
        # Lien du manifest non trouve
        if( self.manifestURL is None ):
            logger.critical( "Pas de lien vers le manifest" )
            sys.exit( -1 )
        # 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.pvtokenKey
        self.pvtoken = "pvtoken=%s~hmac=%s" %( urllib.quote( self.pvtokenData ), hmac.new( self.key, self.pvtokenData, hashlib.sha256 ).hexdigest() )
        
        #
        # Creation de la video
        #
        self.nomFichier      = "%s.flv" %( re.findall( "http://www.pluzz.fr/([^\.]+?)\.html", self.url )[ 0 ] )
        self.premierFragment = 1
        
        # 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 * self.bitrate ) / 6040.0, 0 )
        logger.debug( "Estimation du nombre de fragments : %d" %( self.nbFragMax ) )
        if( self.progressbar and self.nbFragMax != 0 ):
            self.progression = Progression( self.nbFragMax, self.premierFragment )
        else:
            self.progression = ProgressionVide( self.nbFragMax, self.premierFragment )
        
        # Ajout des fragments
        logger.info( "Début du téléchargement des fragments" )
        try :
            i = self.premierFragment
            for i in xrange( self.premierFragment, 99999 ):
                frag = self.navigateur.getFichier( "%s%d?%s&%s&%s" %( self.urlFrag, i, self.pvtoken, self.hdntl, self.hdnea ) )
                # Saute le header "fragment"
                start = frag.find( "mdat" ) + 4
                if i > 1:
                    # A part pour le premier fragment, saute les deux premiers tags
                    for tmp in range(2):
                        bodylen, = struct.unpack_from( '>L', frag, start )
                        bodylen &= 0x00ffffff
                        start += bodylen + 11 + 4
                # Ajoute le reste
                self.fichierVideo.write( frag[ start : ] )
                # Affichage de la progression
                self.progression.afficher()
        except urllib2.URLError, e :
            if( hasattr( e, 'code' ) ):
                if( e.code == 403 ):
                    self.historique.ajouter( Video( lien = self.urlFrag, fragments = i, finie = False ) )
                    logger.critical( "Impossible de charger la vidéo" )
                elif( e.code == 404 ):
                    self.progression.afficherFin()
                    self.historique.ajouter( Video( lien = self.urlFrag, fragments = i, finie = True ) )
                    logger.info( "Fin du téléchargement" )
        except :
            self.historique.ajouter( Video( lien = self.urlFrag, fragments = i, finie = False ) )
        else :
            # Fermeture du fichier
            self.fichierVideo.close()
        
    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 )
            self.pv2       = arbre.find( "{http://ns.adobe.com/f4m/1.0}pv-2.0" ).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 )

    def ouvrirNouvelleVideo( self ):
        try :
            # Ouverture du fichier
            self.fichierVideo = open( self.nomFichier, "wb" )
        except :
            logger.critical( "Impossible d'écrire dans le répertoire %s" %( os.getcwd() ) )
            sys.exit( -1 )
        # Ajout de l'en-tête FLV
        self.fichierVideo.write( binascii.a2b_hex( "464c56010500000009000000001200010c00000000000000" ) )
        # Ajout de l'header du fichier
        self.fichierVideo.write( self.flvHeader )
        self.fichierVideo.write( binascii.a2b_hex( "00000000" ) ) # Padding pour avoir des blocs de 8
        
    def ouvrirVideoExistante( self ):
        try :
            # Ouverture du fichier
            self.fichierVideo = open( self.nomFichier, "a+b" )
        except :
            logger.critical( "Impossible d'écrire dans le répertoire %s" %( os.getcwd() ) )
            sys.exit( -1 )
        
class PluzzDLInfosHandler( xml.sax.handler.ContentHandler ):
    
    def __init__( self, pluzzdl ):
        self.pluzzdl = pluzzdl
        
        self.isUrl = False
        self.isDRM = False
        
    def startElement( self, name, attrs ):
        if( name == "url" ):
            self.isUrl = True
        elif( name == "drm" ):
            self.isDRM = True
    
    def characters( self, data ):
        if( self.isUrl ):
            if( data[ : 3 ] == "mms" ):
                self.pluzzdl.lienMMS = data
            elif( data[ : 4 ] == "rtmp" ):
                self.pluzzdl.lienRTMP = data
            elif( data[ -3 : ] == "f4m" ):
                self.pluzzdl.manifestURL = data
        elif( self.isDRM ):
            self.pluzzdl.drm = data
            
    def endElement( self, name ):
        if( name == "url" ):
            self.isUrl = False
        elif( name == "drm" ):
            self.isDRM = False

class ProgressionVide( object ):
    
    def __init__( self, nbMax, indice = 1 ):
        self.nbMax  = nbMax
        self.indice = indice
        self.old    = 0
        self.new    = 0
    
    def afficher( self ):
        pass
        
    def afficherFin( self ):
        pass
    
class Progression( ProgressionVide ):
    
    def __init__( self, nbMax, indice ):
        ProgressionVide.__init__( self, nbMax, indice )
        
    def afficher( self ):
        self.new = min( int( ( self.indice / self.nbMax ) * 100 ), 100 )
        if( self.new != self.old ):
            logger.info( "Avancement : %3d %%" %( self.new ) )
            self.old = self.new
        self.indice += 1
    
    def afficherFin( self ):
        if( self.old < 100 ):
            logger.info( "Avancement : %3d %%" %( 100 ) )

En détail pour ceux que ca interesse:
Pour les fragments > 1 on doit sauter les deux premiers blocs de données (=tags).
Avec cette version on lit le minimum pour se déplacer de tag en tag (=la longueur de son contenu) et on avance jusqu'au 3°.
A comparer la version avec "\x0f\x09" assume plusieurs choses:
  - le deuxième tag fait 15 octets de long (cad fini par \x0f)
  - le troisième tag est de type video (cad commence par \x09)
  - "\x0f\x09" n'est pas présent dans le fichier avant ce point

Le code:

bodylen, = struct.unpack_from( '>L', frag, start )

=> extrait les 4 premiers octets sous forme de "long"

bodylen &= 0x00ffffff

=> met à zero le premier octet (qui correspond au type du tag) pour ne garder que les octets 2, 3 et 4 = la longueur du contenu

start += bodylen + 11 + 4

=> avance de bodylen + 11 = la taille du header du tag + 4 = la taille du footer du tag (= la longueur totale d'ailleurs...)

Hors ligne

#1767 Le 09/05/2012, à 15:36

k3c

Re : TVDownloader: télécharger les médias du net !

@ miztadux

J'ai fait ta modif, j'ai un fichier elysee-2012-la-vraie-campagne.flv bien lisible.

Ton idée de coller au format flv me semble très saine :-)

Je vais faire d'autres tests.


Debian 12 sur Thinkpad reconditionné

Hors ligne

#1768 Le 09/05/2012, à 15:47

Babar21

Re : TVDownloader: télécharger les médias du net !

Comme K3c, ça a l'air de fonctionner maintenant, merci encore, vous êtes des génies ! smile

Hors ligne

#1769 Le 09/05/2012, à 19:33

chaoswizard

Re : TVDownloader: télécharger les médias du net !

Effectivement, ça semble pas mal (j'obtiens les mêmes début de fichiers pour les premiers fragments d'une vidéo donnée).
Je ne sais pas pourquoi cela varie après mais si ça marche avec la nouvelle méthode de calcul, tant mieux ! (avec la connexion que j'ai, je ne peux pas tenter de prendre vos vidéos de 2h hmm).

Je vais mettre à jour ça !

Merci miztadux !

(c'est souvent que les gens qui participent viennent juste de créer un pseudo, je ne comprends pas trop pourquoi...)

Dernière modification par chaoswizard (Le 09/05/2012, à 19:33)


Ubuntu ==> Debian ==> Archlinux

Hors ligne

#1770 Le 09/05/2012, à 22:12

ubuntuzer

Re : TVDownloader: télécharger les médias du net !

Merci miztadux pour tes modifs sur la v0.8.1
cela m'a permis de récupérer http://www.pluzz.fr/cash-investigation- … 22h25.html
avec la version 0.8, j'obtenais une erreur 403 au bout de quelques Mo.

Hors ligne

#1771 Le 10/05/2012, à 19:49

thom83

Re : TVDownloader: télécharger les médias du net !

Pour moi, avec Lucid i386, le nouveau PluzzDL.py ne fonctionne pas. En effet, python se plaint dés le début :

thom@provence:~$ pluzzdl -vf http://www.pluzz.fr/cash-investigation-2012-05-04-22h25.html
Traceback (most recent call last):
  File "/usr/share/pluzzdl/main.py", line 24, in <module>
    from PluzzDL        import PluzzDL
  File "/usr/share/pluzzdl/PluzzDL.py", line 26, in <module>
    from Historique import Historique, Video
ImportError: No module named Historique

Je neutralise donc les deux premières lignes où apparaît le mot «Historique» en les commentant (lignes 26 et 45) et je relance la commande.
Comme je n'ai rien touché aux différents «self.historique.» présents plus bas, j'ai une nouvelle plainte au bout de 45 Mio de parcours :

Traceback (most recent call last):
  File "/usr/share/pluzzdl/main.py", line 74, in <module>
    PluzzDL( args[ 0 ], options.fragments, options.proxy, options.progressbar )
  File "/usr/share/pluzzdl/PluzzDL.py", line 144, in __init__
    self.historique.ajouter( Video( lien = self.urlFrag, fragments = i, finie = False ) )
AttributeError: 'PluzzDL' object has no attribute 'historique'

Je vais donc essayer de n'aporter que le minimum de changement au fichier d'origine.

Hors ligne

#1772 Le 10/05/2012, à 20:42

chaoswizard

Re : TVDownloader: télécharger les médias du net !

Normal, il manque le fichier Historique.py ...
Il ne faut pas mettre à jour que le fichier Pluzzdl.py, il faut bien prendre tous les fichiers... (en utilisant par exemple un deb ou le tar.gz)

Dernière modification par chaoswizard (Le 10/05/2012, à 20:42)


Ubuntu ==> Debian ==> Archlinux

Hors ligne

#1773 Le 11/05/2012, à 14:58

thom83

Re : TVDownloader: télécharger les médias du net !

Effectivement, la version 0.8.2 fonctionne bien (essayé avec cash-investigation). Avec ubuntu 10.04, il faut ajouter le paquet python-argparse suite à l'abandon du module optparse dans main.py.

Encore merci aux développeurs.

Hors ligne

#1774 Le 11/05/2012, à 15:01

thom83

Re : TVDownloader: télécharger les médias du net !

Effectivement, la version 0.8.2 fonctionne bien (essayé avec cash-investigation). Avec ubuntu 10.04, il faut ajouter le paquet python-argparse suite à l'abandon du module optparse dans main.py.

Encore merci aux développeurs.

Hors ligne

#1775 Le 11/05/2012, à 16:14

k3c

Re : TVDownloader: télécharger les médias du net !

J'ai mis dans le forum Trucs astuces et scripts utiles
un script Python qui surveille le répertoire ~/TVDownloader
http://forum.ubuntu-fr.org/viewtopic.ph … 1#p9218931

et quand un fichier .mp4 y arrive, je le convertis en .mkv, vu que ma platine de salon Dune lit tous les formats sauf le .mp4

Ca pourra peut-être intéresser certains.


Debian 12 sur Thinkpad reconditionné

Hors ligne