Lecture de code de m3u8, une bibliothèque de manipulation de fichiers m3u8 au format vidéo HLS avec Python

Cet article est tiré de adventer Python Advent Calendar 2015 Ceci est l'article du 14ème jour de. (Qiita a également un autre Calendrier de l'Avent Python)

Récemment, j'ai utilisé le package m3u8 parce que j'ai touché le code lié à la vidéo pour diverses raisons, je vais donc essayer de lire le code pour m3u8.

HTTP Live Streaming

HLS (HTTP Live Streaming) est un protocole de distribution de streaming vidéo. Pour les vidéos normales telles que mp4, une vidéo est écrite dans un fichier. HLS divise la vidéo et la lit tout en la téléchargeant. Il existe trois fichiers associés lors de la diffusion de vidéos avec HLS.

type extension Contenu
fichier ts ts Ce sont les données vidéo réelles. Il y en a généralement plusieurs.
fichier m3u8 m3u8 Contient les métadonnées vidéo telles que l'ordre dans lequel les données vidéo doivent être lues.
clé rien de spécial Il s'agit de la clé composite lors du chiffrement du fichier ts.

Vous pourrez peut-être spécifier un autre fichier m3u8 dans le fichier m3u8 (mais je n'écrirai pas cela cette fois).

fichiers m3u8 et bibliothèques m3u8

Par exemple, un fichier texte dans ce format.

#EXTM3U
#EXT-X-KEY:METHOD=AES-256,URI="http://example.com/keyfile",IV=000000000000000
#EXTINF:2,"aaa"
http://example.com/1.ts
#EXTINF:2,
http://example.com/2.ts
#EXTINF:2,
http://example.com/3.ts
#EXTINF:2,
http://example.com/4.ts

Bien sûr, vous souhaitez le générer dynamiquement. Par exemple, le fichier de clé est un fichier m3u8 qui est une URL temporaire du fichier ts. La bibliothèque qui peut être utilisée dans de tels cas est m3u8. Cette fois, nous coderons cette bibliothèque m3u8.

Github: https://github.com/globocom/m3u8 PyPI: https://pypi.python.org/pypi/m3u8

Procédure d'installation et utilisation (vraiment juste en touchant)

Comment l'utiliser est écrit correctement dans READ ME etc., donc ici je vais seulement le toucher.

Installation

::

$ pip install m3u8 

Entrez normalement.

Cela semble dépendre de iso8601. https://github.com/globocom/m3u8/blob/master/requirements.txt#L1 iso8601 est une norme pour la notation de la date et de l'heure.

Comment utiliser

Je vais l'importer pour le moment.

>>> import m3u8

À partir de maintenant, on suppose que m3u8 a déjà été importé dans le shell interactif Python.

Analyse de fichier

Supposons que le fichier m3u8 que vous venez de voir se trouve dans le répertoire courant avec le nom playlist.m3u8.

$ cat playlist.m3u8 
#EXTM3U
#EXT-X-KEY:METHOD=AES-256,URI="http://example.com/keyfile",IV=000000000000000
#EXTINF:2,"aaa"
http://example.com/1.ts
#EXTINF:2,
http://example.com/2.ts
#EXTINF:2,
http://example.com/3.ts
#EXTINF:2,
http://example.com/4.ts
$

Essayez de charger ce fichier sur le shell interactif.

>>> playlist = m3u8.load('./playlist.m3u8')
['METHOD=AES-256', 'URI="http://example.com/keyfile"', 'IV=000000000000000']
>>> playlist
<m3u8.model.M3U8 object at 0x1024b97f0>

L'objet m3u8.model.M3U8 est renvoyé.

Vous pouvez accéder à chaque URL vidéo dans .segments.

>>> playlist.segments
[<m3u8.model.Segment object at 0x10292f3c8>, <m3u8.model.Segment object at 0x10292f400>, <m3u8.model.Segment object at 0x10292f4a8>, <m3u8.model.Segment object at 0x10292f518>]

Vous pouvez modifier les informations de lecture et les générer en manipulant les attributs.

Exporter vers un fichier

Écrivons l'objet playlist dans un fichier appelé output.m3u8.

>>> playlist.dump('./output.m3u8')

Vérifiez la sortie.

$ cat output.m3u8 
#EXTM3U
#EXT-X-KEY:METHOD=AES-256,URI="http://example.com/keyfile",IV=000000000000000
#EXTINF:2,"aaa"
http://example.com/1.ts
#EXTINF:2,
http://example.com/2.ts
#EXTINF:2,
http://example.com/3.ts
#EXTINF:2,
http://example.com/4.ts%                                                                                                                                                             $ 

Il est en cours de sortie.

Lecture de code

Surplombant le tout

Pour le moment, vérifiez la configuration globale du référentiel.

$ tree
.
├── LICENSE
├── MANIFEST.in
├── README.rst
├── m3u8
│   ├── __init__.py
│   ├── model.py
│   ├── parser.py
│   └── protocol.py
├── requirements-dev.txt
├── requirements.txt
├── runtests
├── setup.py
└── tests
    ├── m3u8server.py
    ├── playlists
    │   ├── relative-playlist.m3u8
    │   └── simple-playlist.m3u8
    ├── playlists.py
    ├── test_loader.py
    ├── test_model.py
    ├── test_parser.py
    ├── test_strict_validations.py
    └── test_variant_m3u8.py

3 directories, 20 files

Hmmm, il semble qu'il n'y ait que quatre sources réelles. Leur taille est comme ça.

$ wc -l m3u8/*.py
      75 m3u8/__init__.py
     674 m3u8/model.py
     261 m3u8/parser.py
      22 m3u8/protocol.py
    1032 total

Cela ressemble à un peu plus de 1000 lignes en tout.

load()/loads()

Découvrons le code de la charge que nous avons utilisée précédemment.

m3u8.load() https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/init.py#L35-L43

    if is_url(uri):
        return _load_from_uri(uri)
    else:
        return _load_from_file(uri)

Il semble que le processus soit ramifié en vérifiant si uri est url en interne. Celui qui a réussi plus tôt est probablement _load_from_file (uri). Il semble que vous puissiez également le spécifier avec une URL, je vais donc l'essayer.

>>> m3u8.load('https://gist.githubusercontent.com/TakesxiSximada/04189f4f191f55edae90/raw/1ecab692886508db0877c0f8531bd1f455f83795/m3u8%2520example')
<m3u8.model.M3U8 object at 0x1076b3ba8>

Oh, on dirait que tu peux le faire. Il semble avoir été obtenu par urlopen (). https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/init.py#L46-L53

À propos, il existe également m3u8.loads (), vous pouvez donc également créer un objet M3U8 à partir d'une chaîne. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/init.py#L28-L33

>>> playlist_str = '#EXTM3U\n#EXT-X-KEY:METHOD=AES-256,URI="http://example.com/keyfile",IV=000000000000000\n#EXTINF:2,"aaa"\nhttp://example.com/1.ts\n#EXTINF:2,\nhttp://example.com/2.ts\n#EXTINF:2,\nhttp://example.com/3.ts\n#EXTINF:2,\nhttp://example.com/4.ts'
>>> m3u8.loads(playlist_str)
<m3u8.model.M3U8 object at 0x1076cfef0>

m3u8.model.M3U8()

load () et charges () renvoient des objets M3U8. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/init.py#L33 https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/init.py#L53 https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/init.py#L74

La classe M3U8 est définie dans m3u8.model. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L19

La classe M3U8 définit dump () et dumps (), qui peuvent être écrites dans des fichiers ou des chaînes. Cela ressemble à un module json tel qu'un nom de fonction, mais il semble que vous ne pouvez pas passer un objet fichier. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L217-L271

dump () en créera un s'il n'existe pas. C'est plutôt bon. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L260

Une instance de M3U8 possède les attributs suivants:

Nom d'attribut Moule
key m3u8.model.Key
segments m3u8.model.SegmentList
media m3u8.model.MediaList
playlists m3u8.model.PlaylistList (Nom w)
iframe_playlists m3u8.model.PlaylistList (Cela aussi...)

Il y a certaines choses que j'ai un peu de mal à nommer, mais ce sont les principales.

Dans le processus dumps (), nous faisons quelque chose comme écrire s'il y a une valeur de ces attributs. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L222-L254

        output = ['#EXTM3U']
        if self.is_independent_segments:
            output.append('#EXT-X-INDEPENDENT-SEGMENTS')
        if self.media_sequence > 0:
            output.append('#EXT-X-MEDIA-SEQUENCE:' + str(self.media_sequence))
        if self.allow_cache:
            output.append('#EXT-X-ALLOW-CACHE:' + self.allow_cache.upper())
        if self.version:
            output.append('#EXT-X-VERSION:' + self.version)
        if self.key:
            output.append(str(self.key))
        if self.target_duration:
            output.append('#EXT-X-TARGETDURATION:' + int_or_float_to_string(self.target_duration))
        if self.program_date_time is not None:
            output.append('#EXT-X-PROGRAM-DATE-TIME:' + parser.format_date_time(self.program_date_time))
        if not (self.playlist_type is None or self.playlist_type == ''):
            output.append(
                '#EXT-X-PLAYLIST-TYPE:%s' % str(self.playlist_type).upper())
        if self.is_i_frames_only:
            output.append('#EXT-X-I-FRAMES-ONLY')
        if self.is_variant:
            if self.media:
                output.append(str(self.media))
            output.append(str(self.playlists))
            if self.iframe_playlists:
                output.append(str(self.iframe_playlists))

En regardant ici, il semble que self.key et self.playlists peuvent être convertis en chaînes avec str ().

m3u8.mdoel.Key()

Cette classe gère la valeur de EXT-X-KEY au format m3u8. (iv est le vecteur initial) Par exemple, si la clé est générée ou commutée dynamiquement, elle sera reflétée dans le fichier m3u8 de sortie en procédant comme suit.

>>> m3uobj = m3u8.model.M3U8()
>>> print(m3uobj.dumps())
#EXTM3U

>>> key = m3u8.model.Key(method='AES-256', uri='http://example.com/key.bin', base_uri='', iv='0000000')
>>> str(key)
'#EXT-X-KEY:METHOD=AES-256,URI="http://example.com/key.bin",IV=0000000'
>>> m3uobj.key = key
>>> print(m3uobj.dumps())
#EXTM3U
#EXT-X-KEY:METHOD=AES-256,URI="http://example.com/key.bin",IV=0000000


m3u8.model.SegmentList()

https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L386

Le .segments d'une instance de la classe M3U8 n'est pas une liste, mais un objet List Like appelé m3u8.model.SegmentList. Cette classe hérite du type de liste.

>>> type(playlist.segments)
<class 'm3u8.model.SegmentList'>
>>> isinstance(playlist.segments, list)
True

Mettez m3u8.model.Segment () dans l'élément.

m3u8.model.Segment()

https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L310-L383

Cette classe est utilisée pour spécifier le fichier ts. Spécifiez uri ou base_uri. Vous devez également passer la durée. Il s'agit de la durée de lecture du fichier ts en secondes (vous pouvez utiliser un petit nombre).

>>> m3uobj = m3u8.model.M3U8()
>>> m3uobj.segments.append(m3u8.model.Segment(uri='http://example.com/1.ts', base_uri='', duration=1))
>>> m3uobj.segments.append(m3u8.model.Segment(uri='http://example.com/2.ts', base_uri='', duration=1))
>>> print(m3uobj.dumps())
#EXTM3U
#EXTINF:1,
http://example.com/1.ts
#EXTINF:1,
http://example.com/2.ts

À propos, la durée passée dans le constructeur est un paramètre qui peut être omis dans le constructeur avec duration = None, mais si dumps () est omis dans l'état omis, TypeError sera déclenché. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L369

Ce n'est pas un très bon endroit.

Qu'est-ce que base_uri?

Jusqu'à présent, nous avons passé base_uri au constructeur. Jusqu'à présent, je l'utilisais doucement, je me demandais à quoi cela servait, alors je vais l'examiner.

base_uri est utilisé dans m3u8.model.BasePathMixin.

Cette classe a été mélangée dans Segment, Key, Playlist, IFramePlaylist, Media.

    @property
    def absolute_uri(self):
        if self.uri is None:
            return None
        if parser.is_url(self.uri):
            return self.uri
        else:
            if self.base_uri is None:
                raise ValueError('There can not be `absolute_uri` with no `base_uri` set')
            return _urijoin(self.base_uri, self.uri)

Ceux-ci ont des propriétés .absolute_uri. Le comportement change selon que self.uri se présente sous la forme d'une URL. Dans le cas de l'URL, self.uri est utilisé tel quel, et s'il n'est pas au format URL (format de chemin de fichier, etc.), self.base_uri et self.uri sont combinés pour donner la valeur absolue_uri.

Que ce soit une URL ou non est déterminé à l'aide de m3u8.parser.is_url (). Les URL commençant par http: // ou https: // sont considérées comme des URL.

def is_url(uri):
    return re.match(r'https?://', uri) is not None

Essayez le cas où base_uri est utilisé

Spécifiez une valeur non URL pour uri et une valeur semblable à une URL pour base_uri pour Segment.

>>> m3uobj = m3u8.model.M3U8()
>>> m3uobj.segments.append(m3u8.model.Segment(uri='1.ts', base_uri='http://example.com', duration=1))

Vérifions le texte généré par ce code.

>>> print(m3uobj.dumps())
#EXTM3U
#EXTINF:1,
1.ts

... absolu_uri, non utilisé. Il n'y avait aucun autre endroit que le test qui l'utilisait même avec grep. Je me demande s'il se positionne comme une API pour l'extérieur plutôt que d'être utilisé à l'intérieur ...

Mise à jour par lots du segment et d'autres chemins avec base_path

Contrairement à base_uri, base_path est un enfant assez utilisable. Ceci est également défini dans m3u8.model.BasePathMixin. Si vous réécrivez la valeur de l'objet M3U8 lorsque vous souhaitez réécrire l'URI tel que segment, les valeurs des attributs tels que les segments et la clé que possède l'objet M3U8 seront également réécrites.

Par exemple, essayez dumps () sans donner base_path.

>>> m3uobj = m3u8.model.M3U8()
>>> m3uobj.segments.append(m3u8.model.Segment(uri='1.ts', base_uri='', duration=1))
>>> print(m3uobj.dumps())
#EXTM3U
#EXTINF:1,
1.ts

1.ts utilise l'URI passé lors de la génération de Segment (). Ensuite, placez l'URL dans m3uobj.base_path et essayez dumps ().

>>> m3uobj.base_path = 'http://example.com'
>>> print(m3uobj.dumps())
#EXTM3U
#EXTINF:1,
http://example.com/1.ts

Ensuite, il a été généré avec base_path attaché à 1.ts. base_path est implémenté en tant que propriété / setter, et lorsque vous définissez la valeur, la valeur uri est réinitialisée à la propriété uri avec base_path attachée.

https://github.com/globocom/m3u8/blob/master/m3u8/model.py#L290-L294

    @base_path.setter
    def base_path(self, newbase_path):
        if not self.base_path:
            self.uri = "%s/%s" % (newbase_path, self.uri)
        self.uri = self.uri.replace(self.base_path, newbase_path)

Résumé

Pardon. Je suis épuisé. Cependant, les médias et les listes de lecture ont fondamentalement la même structure, et la différence réside dans le type de valeur générée. Si vous en avez envie, je peux mettre à jour la suite. ..

Demain, c'est @ininsanus. Cordialement Onacious !! http://www.adventar.org/calendars/846#list-2015-12-15

Recommended Posts

Lecture de code de m3u8, une bibliothèque de manipulation de fichiers m3u8 au format vidéo HLS avec Python
Lecture de code de Safe, une bibliothèque pour examiner la force des mots de passe en Python
Lecture de code de faker, une bibliothèque qui génère des données de test en Python
Formater automatiquement le code Python avec Vim
uproot: bibliothèque basée sur Python / Numpy pour lire et écrire des fichiers ROOT
À propos de psd-tools, une bibliothèque capable de traiter des fichiers psd en Python
Tapez les annotations pour Python2 dans les fichiers stub!
Automatisez les tâches en manipulant des fichiers en Python
[Édition DSU] Lecture de la bibliothèque AtCoder avec un codeur vert ~ Implémentation en Python ~
J'ai créé un script en python pour convertir des fichiers .md au format Scrapbox
Obtenez un jeton pour conoha avec python
Un outil pour saisir facilement du code Python
Téléchargez des fichiers dans n'importe quel format en utilisant Python
Code de caractères pour la lecture et l'écriture de fichiers csv avec python
[Python] Récupérez les fichiers dans le dossier avec Python
Lire et écrire des fichiers CSV et JSON avec Python
Paramètres de codage Python avec Visual Studio Code
[Python] Manipulation d'éléments dans une liste (tableau) [Trier]
format en python
Je veux écrire en Python! (1) Vérification du format de code
Exemple de code spécifique pour travailler avec SQLite3 en Python
Une collection de code souvent utilisée dans Python personnel
Un mémorandum lors de l'écriture de code expérimental ~ Se connecter en python
Paramètres VS Code pour le développement en Python avec achèvement
Essayez de rechercher un profil d'un million de caractères en Python
Recherche récursive de fichiers et de répertoires en Python et sortie
Expose settings.json pour un codage Python efficace avec VS Code
Publier / télécharger une bibliothèque créée en Python vers PyPI
Définir le proxy pour Python pip (décrit dans pip.ini)
Code python de la méthode k-means super simple
Peut être utilisé avec AtCoder! Une collection de techniques pour dessiner du code court en Python!
Format d'image en Python
Manipulation de fichiers vidéo (ffmpeg)
[Python] Lecture de fichiers CSV
Insérez l'instruction Import requise pour la complétion du code Python dans Neovim
Publication d'une bibliothèque qui masque les données de caractères dans les images Python
Créer un compte enfant de connect with Stripe en Python
Création d'un environnement de développement pour les applications Android - Création d'applications Android avec Python
Un moyen simple d'éviter plusieurs boucles for en Python
Développer une bibliothèque pour obtenir la liste des collections Kindle en Python
Comment définir plusieurs variables dans une instruction Python for
Faites quelque chose comme un interpréteur Python avec Visual Studio Code
Un exemple pour dessiner des points avec PIL (Python Imaging Library).
Créez le code qui renvoie "A et prétendant B" en python
Essayez de créer un réseau de neurones en Python sans utiliser de bibliothèque
Bibliothèque pour spécifier un serveur de noms en python et dig
Un ensemble de fichiers de script qui font wordcloud avec Python3
[Python] Créer un écran pour le code d'état HTTP 403/404/500 avec Django