Code lesen von m3u8, einer Bibliothek zum Bearbeiten von m3u8-Dateien im HLS-Videoformat mit Python

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).

m3u8-Dateien und m3u8-Bibliotheken

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

Installationsverfahren und Verwendung (wirklich nur berühren)

Wie man es benutzt, ist richtig in READ ME etc. geschrieben, also werde ich es hier nur anfassen.

Installation

::

$ 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.

Wie benutzt man

Ich werde es vorerst importieren.

>>> import m3u8

Von nun an wird davon ausgegangen, dass m3u8 bereits in die interaktive Python-Shell importiert wurde.

Dateianalyse

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.

In Datei exportieren

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.

Code lesen

Mit Blick auf das Ganze

Ü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.

Was ist base_uri?

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

Versuchen Sie den Fall, in dem base_uri verwendet wird

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.

Stapelaktualisierung von Segment- und anderen Pfaden mit base_path

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)

Zusammenfassung

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

Code lesen von m3u8, einer Bibliothek zum Bearbeiten von m3u8-Dateien im HLS-Videoformat mit Python
Codelesen von Safe, einer Bibliothek zur Überprüfung der Kennwortstärke in Python
Code lesen von faker, einer Bibliothek, die Testdaten in Python generiert
Formatieren Sie Python-Code automatisch mit Vim
entwurzeln: Python / Numpy-basierte Bibliothek zum Lesen und Schreiben von ROOT-Dateien
Über psd-tools, eine Bibliothek, die psd-Dateien in Python verarbeiten kann
Geben Sie Anmerkungen für Python2 in Stub-Dateien ein!
Automatisieren Sie Jobs, indem Sie Dateien in Python bearbeiten
[DSU] Lesen der AtCoder-Bibliothek mit Green Coder ~ Implementierung in Python ~
Ich habe ein Skript in Python erstellt, um MDD-Dateien in das Scrapbox-Format zu konvertieren
Holen Sie sich ein Zeichen für Conoha mit Python
Ein Tool zur einfachen Eingabe von Python-Code
Laden Sie Dateien in jedem Format mit Python herunter
Zeichencode zum Lesen und Schreiben von CSV-Dateien mit Python ~ Windows-Umgebung ver ~
[Python] Holen Sie sich die Dateien mit Python in den Ordner
Lesen und Schreiben von CSV- und JSON-Dateien mit Python
Einstellungen für die Python-Codierung mit Visual Studio-Code
[Python] Manipulation von Elementen in einer Liste (Array) [Sortieren]
Format in Python
Ich möchte in Python schreiben! (1) Überprüfung des Codeformats
Spezifischer Beispielcode für die Arbeit mit SQLite3 in Python
Eine Code-Sammlung, die häufig in persönlichem Python verwendet wird
Ein Memorandum beim Schreiben von experimentellem Code ~ Anmelden in Python
VS-Code-Einstellungen für die Entwicklung in Python mit Abschluss
Versuchen Sie, in Python nach einem Profil mit einer Million Zeichen zu suchen
Suchen Sie rekursiv nach Dateien und Verzeichnissen in Python und geben Sie sie aus
Stellen Sie settings.json für eine effiziente Python-Codierung mit VS-Code bereit
Veröffentlichen / Hochladen einer in Python erstellten Bibliothek in PyPI
Proxy für Python-Pip festlegen (beschrieben in pip.ini)
Super einfacher Fall k-means Methode Python-Code
Kann mit AtCoder verwendet werden! Eine Sammlung von Techniken zum Zeichnen von Kurzcode in Python!
Bildformat in Python
Bearbeiten von Videodateien (ffmpeg)
[Python] CSV-Dateien lesen
Fügen Sie die Import-Anweisung ein, die für die Vervollständigung des Python-Codes in Neovim erforderlich ist
Veröffentlichung einer Bibliothek, die Zeichendaten in Python-Bildern verbirgt
Erstellen Sie ein untergeordnetes Konto für die Verbindung mit Stripe in Python
Erstellen einer Entwicklungsumgebung für Android-Apps - Erstellen von Android-Apps mit Python
Eine einfache Möglichkeit, mehrere for-Schleifen in Python zu vermeiden
Entwickelte eine Bibliothek, um die Kindle-Sammlungsliste in Python abzurufen
So definieren Sie mehrere Variablen in einer Python for-Anweisung
Machen Sie so etwas wie einen Python-Interpreter mit Visual Studio Code
Ein Beispiel zum Zeichnen von Punkten mit PIL (Python Imaging Library).
Erstellen Sie den Code, der in Python "A und vorgeben B" ausgibt
Versuchen Sie, ein neuronales Netzwerk in Python aufzubauen, ohne eine Bibliothek zu verwenden
Bibliothek zur Angabe eines Nameservers in Python und Dig
Eine Reihe von Skriptdateien, die Wordcloud mit Python3 ausführen
[Python] Erstellen Sie mit Django einen Bildschirm für den HTTP-Statuscode 403/404/500