Dieser Artikel stammt von adventer Python Advent Calendar 2015 Dies ist der Artikel am 14. Tag von. (Qiita hat auch einen anderen Python-Adventskalender)
Vor kurzem habe ich das m3u8-Paket verwendet, weil ich den Video-Code aus verschiedenen Gründen berührt habe. Daher werde ich versuchen, den Code für m3u8 zu lesen.
HTTP Live Streaming
HLS (HTTP Live Streaming) ist ein Video-Streaming-Verteilungsprotokoll. Bei normalen Videos wie mp4 wird ein Video in eine Datei geschrieben. HLS teilt das Video und spielt es ab, während es heruntergeladen wird. Bei der Bereitstellung von Videos mit HLS gibt es drei verwandte Dateien.
Art | Erweiterung | Inhalt |
---|---|---|
ts-Datei | ts | Dies sind die tatsächlichen Videodaten. Es gibt normalerweise mehrere. |
m3u8-Datei | m3u8 | Enthält Videometadaten wie die Reihenfolge, in der Videodaten abgespielt werden sollen. |
Schlüssel | nichts Besonderes | Dies ist der zusammengesetzte Schlüssel beim Verschlüsseln der ts-Datei. |
Möglicherweise können Sie eine andere m3u8-Datei in der m3u8-Datei angeben (diesmal schreibe ich das jedoch nicht).
Zum Beispiel eine Textdatei in diesem 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
Natürlich möchten Sie es dynamisch generieren. Beispielsweise ist die Schlüsseldatei eine m3u8-Datei, die eine temporäre URL der ts-Datei ist. Die Bibliothek, die in solchen Fällen verwendet werden kann, ist m3u8. Dieses Mal werden wir diese m3u8-Bibliothek mit Code lesen.
Github: https://github.com/globocom/m3u8 PyPI: https://pypi.python.org/pypi/m3u8
Wie man es benutzt, ist richtig in READ ME etc. geschrieben, also werde ich es hier nur anfassen.
::
$ pip install m3u8
Geben Sie normal ein.
Es scheint von [iso8601] abzuhängen (https://pypi.python.org/pypi/iso8601). https://github.com/globocom/m3u8/blob/master/requirements.txt#L1 iso8601 ist ein Standard für die Notation von Datum und Uhrzeit.
Ich werde es vorerst importieren.
>>> import m3u8
Von nun an wird davon ausgegangen, dass m3u8 bereits in die interaktive Python-Shell importiert wurde.
Angenommen, die gerade gesehene m3u8-Datei befindet sich im aktuellen Verzeichnis mit dem Namen 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
$
Versuchen Sie, diese Datei in die interaktive Shell zu laden.
>>> playlist = m3u8.load('./playlist.m3u8')
['METHOD=AES-256', 'URI="http://example.com/keyfile"', 'IV=000000000000000']
>>> playlist
<m3u8.model.M3U8 object at 0x1024b97f0>
Das Objekt m3u8.model.M3U8 wird zurückgegeben.
Sie können auf jede Video-URL in .segments zugreifen.
>>> 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>]
Sie können die gelesenen Informationen ändern und ausgeben, indem Sie Attribute bearbeiten.
Schreiben wir das Wiedergabelistenobjekt in eine Datei namens output.m3u8.
>>> playlist.dump('./output.m3u8')
Überprüfen Sie die Ausgabe.
$ 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% $
Es wird ausgegeben.
Überprüfen Sie vorerst die Gesamtkonfiguration des Repositorys.
$ 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, es scheint, dass es nur vier tatsächliche Quellen gibt. Ihre Größe ist so.
$ wc -l m3u8/*.py
75 m3u8/__init__.py
674 m3u8/model.py
261 m3u8/parser.py
22 m3u8/protocol.py
1032 total
Insgesamt sieht es nach etwas mehr als 1000 Zeilen aus.
load()/loads()
Lassen Sie uns den Code aus der zuvor verwendeten Last ausgraben.
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)
Es scheint, dass der Prozess verzweigt ist, indem überprüft wird, ob uri intern eine URL ist. Derjenige, der früher bestanden hat, ist wahrscheinlich _load_from_file (uri). Es scheint, dass Sie es auch mit einer URL angeben können, also werde ich es versuchen.
>>> m3u8.load('https://gist.githubusercontent.com/TakesxiSximada/04189f4f191f55edae90/raw/1ecab692886508db0877c0f8531bd1f455f83795/m3u8%2520example')
<m3u8.model.M3U8 object at 0x1076b3ba8>
Oh, es sieht so aus, als könntest du es schaffen. Es scheint von urlopen () erhalten zu werden. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/init.py#L46-L53
Übrigens gibt es auch m3u8.loads (), sodass Sie auch ein M3U8-Objekt aus einer Zeichenfolge erstellen können. 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 () und load () geben M3U8-Objekte zurück. 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
Die M3U8-Klasse ist in m3u8.model definiert. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L19
Die M3U8-Klasse definiert dump () und dumps (), die in Dateien oder Zeichenfolgen geschrieben werden können. Es sieht aus wie ein JSON-Modul wie ein Funktionsname, aber es scheint, dass Sie kein Dateiobjekt übergeben können. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L217-L271
dump () erstellt eine, wenn sie nicht vorhanden ist. Es ist sehr gut. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L260
Eine Instanz von M3U8 weist die folgenden Attribute auf:
Attributname | Schimmel |
---|---|
key | m3u8.model.Key |
segments | m3u8.model.SegmentList |
media | m3u8.model.MediaList |
playlists | m3u8.model.PlaylistList (Name w) |
iframe_playlists | m3u8.model.PlaylistList (Das auch...) |
Es gibt einige Dinge, bei denen ich Probleme mit der Benennung habe, aber dies sind die wichtigsten.
Im dumps () -Prozess schreiben wir so etwas wie, wenn diese Attribute einen Wert enthalten. 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))
Hier scheint es, dass self.key und self.playlists mit str () in Strings konvertiert werden können.
m3u8.mdoel.Key()
Diese Klasse verwaltet den Wert von EXT-X-KEY im m3u8-Format. (iv ist der Anfangsvektor) Wenn der Schlüssel beispielsweise dynamisch generiert oder umgeschaltet wird, wird er wie folgt in der m3u8-Ausgabedatei wiedergegeben.
>>> 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
Die .segments einer Instanz der M3U8-Klasse sind keine Liste, sondern ein List Like-Objekt mit dem Namen m3u8.model.SegmentList. Diese Klasse erbt vom Listentyp.
>>> type(playlist.segments)
<class 'm3u8.model.SegmentList'>
>>> isinstance(playlist.segments, list)
True
Fügen Sie m3u8.model.Segment () in das Element ein.
m3u8.model.Segment()
https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L310-L383
Diese Klasse wird verwendet, um die ts-Datei anzugeben. Geben Sie uri oder base_uri an. Sie müssen auch die Dauer übergeben. Dies ist die Länge, um die ts-Datei in Sekunden abzuspielen (Sie können eine kleine Zahl verwenden).
>>> 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
Übrigens ist die im Konstruktor übergebene Dauer ein Parameter, der im Konstruktor mit Dauer = Keine weggelassen werden kann. Wenn jedoch dumps () im ausgelassenen Zustand ausgeführt wird, wird TypeError ausgelöst. https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L369
Dies ist kein sehr guter Ort.
Bisher haben wir base_uri an den Konstruktor übergeben. Bis jetzt habe ich es leise benutzt und mich gefragt, wofür es ist, also werde ich es mir ansehen.
base_uri wird in [m3u8.model.BasePathMixin] verwendet (https://github.com/globocom/m3u8/blob/210db9c494c1b703ab7e169d3ae4ed488ec30eac/m3u8/model.py#L273-L294).
Diese Klasse wurde in Segment, Key, Playlist, IFramePlaylist, Media gemischt.
@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)
Diese haben .absolute_uri-Eigenschaften. Das Verhalten ändert sich abhängig davon, ob self.uri in Form einer URL vorliegt. Im Fall von URL wird self.uri unverändert verwendet. Wenn es nicht im URL-Format (Dateipfadformat usw.) vorliegt, werden self.base_uri und self.uri kombiniert, um den Wert absolute_uri zu erhalten.
Ob es sich um eine URL handelt oder nicht, wird mit m3u8.parser.is_url () bestimmt. URLs, die mit http: // oder https: // beginnen, werden als URLs eingestuft.
def is_url(uri):
return re.match(r'https?://', uri) is not None
Geben Sie einen Nicht-URL-Wert für uri und einen URL-ähnlichen Wert für base_uri für Segment an.
>>> m3uobj = m3u8.model.M3U8()
>>> m3uobj.segments.append(m3u8.model.Segment(uri='1.ts', base_uri='http://example.com', duration=1))
Lassen Sie uns den von diesem Code generierten Text überprüfen.
>>> print(m3uobj.dumps())
#EXTM3U
#EXTINF:1,
1.ts
... absolute_uri, nicht verwendet. Es gab keinen anderen Ort als einen Test, der dies auch mit grep verwendete. Ich frage mich, ob es als API für den Außenbereich positioniert ist und nicht für den Innenbereich verwendet wird.
Im Gegensatz zu base_uri ist base_path ein ziemlich brauchbares Kind. Dies ist auch in m3u8.model.BasePathMixin definiert. Wenn Sie den Wert des M3U8-Objekts neu schreiben, wenn Sie die URL wie z. B. das Segment neu schreiben möchten, werden auch die Werte der Attribute wie Segmente und Schlüssel, über die das M3U8-Objekt verfügt, neu geschrieben.
Versuchen Sie beispielsweise dumps (), ohne base_path anzugeben.
>>> 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 verwendet den Uri, der übergeben wurde, als Segment () generiert wurde. Geben Sie als Nächstes die URL in m3uobj.base_path ein und versuchen Sie es mit dumps ().
>>> m3uobj.base_path = 'http://example.com'
>>> print(m3uobj.dumps())
#EXTM3U
#EXTINF:1,
http://example.com/1.ts
Dann wurde es mit base_path ausgegeben, das an 1.ts angehängt ist. base_path wird als Eigenschaft / Setter implementiert. Wenn Sie den Wert festlegen, wird der uri-Wert auf die uri-Eigenschaft zurückgesetzt, an die base_path angehängt ist.
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)
Es tut uns leid. Ich bin erschöpft. Medien und Wiedergabelisten haben jedoch im Grunde die gleiche Struktur, und der Unterschied besteht darin, welche Art von Wert generiert wird. Wenn Sie Lust dazu haben, kann ich die Fortsetzung aktualisieren. ..
Morgen ist @ininsanus. Grüße Onacious !! http://www.adventar.org/calendars/846#list-2015-12-15
Recommended Posts