Statt die Metadaten von Youtube-Videos von Hand zu editieren, greift der clevere Handwerker Michael Schilli in die API-Trickkiste und lässt ein Skript die Arbeit automatisch tun.
Sollte mich einmal jemand in eine Fernsehtalkshow einladen, in der ich dann nach Herzenslust über meine besten Produktivitätstricks [2] schwadronieren dürfte, würde ich dort Folgendes zum Besten geben: Am effektivsten kommt voran, wer jederzeit Änderungen vornehmen kann, die er später – falls sie sich doch als Blindgänger erweisen – ebenso fix und beinahe verlustlos wieder zurückrollt.
Die Software-Entwicklung mit Git ist dafür ein gutes Beispiel: Da lassen sich wagemutig und mit großen Schritten neue Funktionen in Programme einfügen oder Code großspurig neu arrangieren. Falls das Ganze sich nach einer Weile als Schnapsidee herausstellt, verwirft der Entwickler in einer Sekunde alles wieder, ohne dass jemand überhaupt mitbekommt, welchen Hirngespinsten er zwischenzeitlich nachjagte.
Deshalb versioniere ich schlicht alles, was ich produziere. Mein Blogbeiträge, meine Artikel, meine Programme: Das Versionskontrollsystem erlaubt es jederzeit, zum Stand von gestern zurückzukehren, aus welchen Gründen auch immer.
Nur mit meinen Youtube-Videos klappte das Verfahren bisher nicht. Ändere ich zum Beispiel den Titel oder auch nur ein Tag einer meiner international geschätzten Qualitätsfilmchen (Abbildung 1), kann ich Youtube später nicht einfach mitteilen: Heute habe ich nur Blödsinn produziert, ich möchte deshalb, dass alles wieder so aussieht wie gestern.
Metadaten mit Version
Daher möchte ich die Metadaten meiner Youtube-Existenz einer lokalen Yaml-Datei (Listing 1) anvertrauen und diese mit Git versionieren. Ein Skript geht dann regelmäßig über die Einträge der Datei, liest die ID jedes Videos aus den Yaml-Daten und sieht auf Youtube nach, ob alles entsprechend der Datei eingerichtet ist, also ob der Titel in der Datei mit den Metadaten zum Video übereinstimmt. Bei Abweichungen passt das Skript die Youtube-Daten den Yaml-Daten an. Listing 1 definiert in einer langen Liste eine Reihe von Videos mit ihren IDs und Titeln. Es ließe sich leicht mit Tags, Thumbnail-Image, Erscheinungsdatum oder anderen Metadaten erweitern.
Listing 1
videos.yaml
1 videos: 2 - id: _3i5yVoTvCs 3 title: "How to flip German pancakes" 4 - id: brPfE66FC24 5 title: "Tivo Stream Cooling Fan Replacement" 6 - id: 2qxXhW7RxsY 7 title: "Rio Portable Beach Shelter Assembly Instructions"
Im Auftrag des Users
Bevor das Skript aber auf die Userdaten zugreifen oder diese gar verändern darf, muss Google der neuen Applikation Zugriff auf die Daten des Users gewähren, schließlich dürfen nicht Hinz und Kunz mit meinen Videos herumfuhrwerken. Google holt das Einverständnis des Users ein, indem Listing 3 per Google-API [3] den User mit dem Browser auf eine Google-Seite lotst, auf der Letzterer sich einloggt und anschließend bestätigt, dass die Applikation tatsächlich Zugriffsrechte besitzt (Abbildung 2).
Listing 3
youtube-sync
01 #!/usr/bin/python
02 import httplib2
03 import os
04 import sys
05 import yaml
06
07 from apiclient.discovery import build
08 from apiclient.errors import HttpError
09 from oauth2client.client import \
10 flow_from_clientsecrets
11 from oauth2client.file import Storage
12 from oauth2client.tools import \
13 argparser, run_flow
14
15 CLIENT_SECRETS_FILE = "client-secrets.json"
16 YOUTUBE_READ_WRITE_SCOPE = \
17 "https://www.googleapis.com/auth/youtube"
18 YOUTUBE_API_SERVICE_NAME = "youtube"
19 YOUTUBE_API_VERSION = "v3"
20
21 def get_authenticated_service(args):
22 flow = flow_from_clientsecrets(
23 CLIENT_SECRETS_FILE,
24 scope=YOUTUBE_READ_WRITE_SCOPE)
25
26 storage = Storage("oauth2.json");
27 credentials = storage.get()
28
29 if credentials is None or \
30 credentials.invalid:
31 credentials = \
32 run_flow(flow, storage, args)
33
34 return build(YOUTUBE_API_SERVICE_NAME,
35 YOUTUBE_API_VERSION,
36 http=credentials.authorize(
37 httplib2.Http()))
38
39 def video_update(youtube, id, title):
40 response = youtube.videos().list(
41 id=id, part='snippet').execute()
42
43 if not response["items"]:
44 print("Video '%s' was not found." % id)
45 sys.exit(1)
46
47 snippet = response["items"][0]["snippet"]
48
49 if snippet['title'] == title:
50 print("%s: Unchanged" % id)
51 return
52
53 snippet['title'] = title
54
55 try:
56 youtube.videos().update(
57 part='snippet',
58 body=dict(
59 snippet=snippet, id=id)).execute()
60 except HttpError, e:
61 print("HTTP error %d: %s" % \
62 (e.resp.status, e.content))
63 else:
64 print("Updated OK")
65
66 if __name__ == "__main__":
67 args = argparser.parse_args()
68 youtube = get_authenticated_service(args)
69
70 stream = open("videos.yaml", "r")
71 all = yaml.load(stream)
72 for video in all['videos']:
73 video_update(youtube, video['id'],
74 video['title'])

Abbildung 2: Beim ersten Aufruf öffnet das Skript einen Browser, der nach dem Einverständnis des Users fragt.
Hierzu legt der API-Jockey auf der Google Cloud Platform Console [3] ein neues Projekt an (Abbildung 3). Dann navigiert er zu »Create Credentials« (Abbildung 4) und wählt »OAuth client ID« aus [4] (nicht »API key«, der dient nur zur Projektverwaltung). Da es sich um ein Desktop-Programm und keine Webapplikation handelt, ist im Auswahl-Menü zur Applikationsart »Other« auszuwählen. Die dann von Google produzierten Strings für »Client ID« und »Client Secret« (Abbildung 5) sind in eine Json-Datei nach Listing 2 einzutragen.
Listing 2
client-secrets.json
1 {
2 "installed": {
3 "client_id": "XXX",
4 "client_secret": "YYY",
5 "redirect_uris": ["http://localhost", "urn:ietf:wg:oauth:2.0:oob"],
6 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
7 "token_uri": "https://accounts.google.com/o/oauth2/token"
8 }
9 }
Nach dem ersten Lauf des Skripts und nachdem der User im Browser den Zugriff erfolgreich bestätigt hat, verzweigt der Browser auf eine Seite mit dem Inhalt »The authentication flow has completed« und das Skript legt in der Datei »oauth2.json« einen Oauth2-Access-Token ab, der ihm bei künftigen Aufrufen Zugriff auf die Userdaten gewährt, ohne dass der User erneut einwilligen muss.
Das funktioniert so lange, bis der Access-Token ausläuft. Das entsprechende Verfallsdatum ist ebenfalls in der Json-Datei vermerkt. Zudem enthält die Datei einen Refresh-Token, mit dem das Skript nach Ablauf der Gültigkeit des Access-Token einen frischen anfordern kann. Was praktisch endlos funktioniert – es sei denn, der User begibt sich auf die Google Console (Abbildung 5) und entzieht dem Client den Zugriff, dann dreht Google den Hahn zu.
Die Funktion »get_authenticated_service()« ab Zeile 21 in Listing 3 definiert als Scope, also als Reichweite des Zugriffs, »YOUTUBE_READ_WRITE_SCOPE« und fordert daher Lese- und Schreibrechte an. Die Interaktion mit dem Browser und den Oauth2-Tokentanz dahinter hat Google schön im SDK abstrahiert, das Skript ruft nur die Funktionen »flow_from_clientsecrets()« und »run_ flow()« aus den beiden Paketen »oauth2client.client« und »oauth2client.tools« auf. Der Ringelreihen mit dem Browser klappt sowohl auf Linux als auch auf dem Mac gleich gut.
Zeile 69 liest die Yaml-Datei mit den lokal gehaltenen Video-Metadaten ein, Zeile 71 iteriert über alle dort gefundenen Videos. Für jedes ruft es die ab Zeile 39 definierte Funktion »video_update()« auf, die mit »youtube.videos()« zunächst Metadaten des mittels seiner ID bezeichneten User-Videos einholt. Sie beschränkt sich auf den Bereich »snippet«, womit Youtube Json-Daten mit Titel, Tags, Beschreibung und einigen Feldern mehr bezeichnet.
Aus diesen Metadaten holt Zeile 49 dann den Titel des Videos und vergleicht ihn mit der lokalen Version. Stimmen beide Titel nicht überein, stößt Zeile 56 in einem Try-Block die »update()«-Methode an, die als Parameter die ursprünglich eingeholten Metadaten mit dem angepassten neuen Titel beigepackt erhalten. Tritt ein Fehler beim Übertragen auf, druckt Zeile 61 die HTTP-Fehlermeldung, sonst meldet Zeile 64 »Updated OK« – und die Metadaten auf Youtube entsprechen nun exakt den lokal vorgehaltenen im Versionierungssystem.
Installation und Ausblick
Das nötige SDK zur Inbetriebnahme des Skripts ist als Python-Paket im Standardrepository erhältlich und via »pip« installierbar:
pip install --user --upgrade google-api-python-client
Nach dem ersten Lauf des Skripts – es spannt das eingangs gezeigte Browserfenster auf und holt dann das Einverständnis des Users ein – produziert es mit der Yaml-Datei in Listing 1 die folgende Ausgabe:
$ ./youtube-sync _3i5yVoTvCs: Unchanged brPfE66FC24: Unchanged 2qxXhW7RxsY: Unchanged
Da alle Titel-Strings in den eingeholten Youtube-Metadaten den Yaml-Daten entsprechen, nimmt das Skript keine Anpassungen vor. Ändert sich jedoch ein Titeltext, pumpt das Skript ihn zu Youtube, das die Metadaten des Videos auffrischt. Das Skript bestätigt dies mit »Updated OK«.
Das Skript lässt sich beliebig erweitern, so kann es auch Videos auf der Festplatte bei Bedarf hochladen und so den lokalen Status der Filmsammlung mit jedem Lauf automatisch mit den öffentlich zugänglichen Videos auf Youtube abgleichen. Versioniert der User die lokale Sammlung und ihre Metadaten mit einem Versionskontrollsystem, kann er zeitlich vor- und zurückspringen, Änderungen vornehmen und zurückrollen, falls sich eine Idee als nicht so glorreich erweist.
Online PLUS
Im Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/Ausgaben/2018/01/plus
Infos
-
Listings zu diesem Artikel: https://www.linux-magazin.de/static/listings/magazin/2018/01/snapshot/
-
Michael Schilli, “Schaut auf diese Stadt”: Linux-Magazin 12/16, S. 104, https://www.linux-magazin.de/Ausgaben/2016/12/Perl-Snapshot
-
Google Cloud Platform Console: https://console.cloud.google.com/apis
-
“OAuth 2.0 for Mobile & Desktop Apps”: https://developers.google.com/identity/protocols/OAuth2InstalledApp]










