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).
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
Comment l'utiliser est écrit correctement dans READ ME etc., donc ici je vais seulement le toucher.
::
$ 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.
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.
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.
É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.
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.
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
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 ...
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)
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