Sollte mich einmal jemand in eine Fernsehshow einladen, in der ich nach Herzenslust über meine besten Produktivitätstricks schwadronieren dürfte, würde ich verkünden: Am effektivsten kommt man voran, wenn man schnell Änderungen vornimmt, die man, falls sie sich als Blindgänger herausstellen, beinahe verlustlos zurückrollen kann.
Software-Entwicklung mit Git ist so ein Beispiel: Da kann ich wagemutig mit großen Schritten neue Funktionen in meine Programme einfügen oder den Code großspurig neu arrangieren, aber falls das Ganze sich nach einer Weile als Schnapsidee herausstellt, in einer Sekunde alles wieder verwerfen, ohne dass überhaupt jemand mitbekommt, welchen Hirngespinsten ich nachgejagt bin.
Deshalb wird alles, was ich produziere, versioniert. Mein Blog, meine Artikel, meine Programme: das Versionskontrollsystem erlaubt es jederzeit, zum Stand von Gestern zurückzukehren, aus welchem Grund auch immer. Nur mit meinen Youtube-Videos klappte das bisher nicht, ändere ich zum Beispiel den Titel oder ein Tag einer meiner international geschätzten Qualitätsfilmchen (Abbildung 1), kann ich Youtube nicht mitteilen: Heute habe ich nur Blödsinn produziert, ich möchte, dass alles wieder so aussieht wie gestern.
|
| Abbildung 1: Der Autor als pfannenkuchenwerfender Küchenchef auf Youtube. |
Deswegen dachte ich mir, dass ich die Metadaten meiner Youtube-Existenz doch einfach einer lokalen Yaml-Datei (Listing 1) anvertrauen und diese mit Git versionieren könnte. Ein Skript ginge dann regelmäßig über die Einträge der Datei, läse die ID jedes Videos aus den Yaml-Daten, sähe auf Youtube nach, ob dort alles entsprechend der Datei eingerichtet ist, also ob der Titel in der Datei mit den von Youtube angezeigten Metadaten zum Video übereinstimmt. Bei Abweichungen passt das Skript die Youtube-Daten einfach entsprechend den Yaml-Daten an. Nichts leichter als das, oder? 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, Beschreibung oder anderen Metadaten erweitern.
001 videos:
002 - id: fhNAzqwy73g
003 snippet.title: '"Virale Youtube-Videos melden", Linux Magazin 2015-01, Programmier-Snapshot, Michael Schilli'
004 status.privacyStatus: unlisted
005 - id: 5UKeXs13-sk
006 snippet.title: '"Wettervorhersage für Pendler", Linux Magazin 2015-02, Programmier-Snapshot, Michael Schilli'
007 status.privacyStatus: unlisted
008 - id: 7fDcyfc5ifE
009 snippet.title: '"Hotkeys auf Ubuntu mit Autokey", Linux Magazin 2015-03, Programmier-Snapshot, Michael Schilli'
010 status.privacyStatus: unlisted
011 - id: giPzvu-b21g
012 snippet.title: '"Die Github-API", Linux Magazin 2015-04, Programmier-Snapshot, Michael Schilli'
013 status.privacyStatus: unlisted
014 - id: xMM8XjBA3A8
015 snippet.title: '"Der Zeitgeist-Dämon auf Ubuntu", Linux Magazin 2015-05, Programmier-Snapshot, Michael Schilli'
016 status.privacyStatus: unlisted
017 - id: cBh3McHGKM0
018 snippet.title: '"Das Sticky-Bit", Linux Magazin 2015-06, Programmier-Snapshot, Michael Schilli'
019 status.privacyStatus: unlisted
020 - id: vCUwP7sIy1M
021 snippet.title: '"Statistischer p-Wert", Linux Magazin 2015-07, Programmier-Snapshot, Michael Schilli'
022 status.privacyStatus: unlisted
023 - id: D5PSGDoNdKM
024 snippet.title: '"Kombinatorik-Rätsel", Linux Magazin 2015-08, Programmier-Snapshot, Michael Schilli'
025 status.privacyStatus: unlisted
026 - id: GTLMy8S8xQA
027 snippet.title: '"Netzwerksniffer mit UI", Linux Magazin 2015-09, Programmier-Snapshot, Michael Schilli'
028 status.privacyStatus: unlisted
029 - id: srCrkrZRwio
030 snippet.title: '"Configuration Management mit Ansible", Linux Magazin 2015-10, Programmier-Snapshot, Michael Schilli'
031 status.privacyStatus: unlisted
032 - id: Ckf6xVm7HC8
033 snippet.title: '"Elasticsearch API", Linux Magazin 2015-11, Programmier-Snapshot, Michael Schilli'
034 status.privacyStatus: unlisted
035 - id: 2hKh7v6Rh-Q
036 snippet.title: '"Etiketten drucken Dymo Labelwriter", Linux Magazin 2012-15, Programmier-Snapshot, Michael Schilli'
037 status.privacyStatus: unlisted
038 - id: dB-6axjWsFg
039 snippet.title: '"Programmiertricks", Linux Magazin 2016-01, Programmier-Snapshot, Michael Schilli'
040 status.privacyStatus: unlisted
041 - id: saqiF40KMMA
042 snippet.title: '"Z-Wave Controller/Switch ansteuern", Linux Magazin 2016-02, Programmier-Snapshot, Michael Schilli'
043 status.privacyStatus: unlisted
044 - id: 0Zr35ea1X48
045 snippet.title: '"Garmin 64s Navidaten aufbereiten", Linux Magazin 2016-03, Programmier-Snapshot, Michael Schilli'
046 status.privacyStatus: unlisted
047 - id: OyS-V4vJF58
048 snippet.title: '"WLAN-Alarme aufs Telefon", Linux Magazin 2016-04, Programmier-Snapshot, Michael Schilli'
049 status.privacyStatus: unlisted
050 - id: FEAVn9MkLsw
051 snippet.title: '"Druckerfunktionstest Distro-Updates", Linux Magazin 2016-05, Programmier-Snapshot, Michael Schilli'
052 status.privacyStatus: unlisted
053 - id: QtcYN7TYqQ0
054 snippet.title: '"Ebay-Abrechnungen automatisch prüfen", Linux Magazin 2016-06, Programmier-Snapshot, Michael Schilli'
055 status.privacyStatus: unlisted
056 - id: IG_zuoi1rSE
057 snippet.title: '"WeMo Fernschalter mit ifttt.com", Linux Magazin 2016-07, Programmier-Snapshot, Michael Schilli'
058 status.privacyStatus: unlisted
059 - id: 3V67v5PrT54
060 snippet.title: '"Mathematische Stoppprobleme", Linux Magazin 2016-08, Programmier-Snapshot, Michael Schilli'
061 status.privacyStatus: unlisted
062 - id: foUzO4Ga4hw
063 snippet.title: '"Superpositions mit Perl6", Linux Magazin 2016-09, Programmier-Snapshot, Michael Schilli'
064 status.privacyStatus: unlisted
065 - id: gBTTAey5QHI
066 snippet.title: '"Fahrdaten der Automatic-API auslesen", Linux Magazin 2016-10, Programmier-Snapshot, Michael Schilli'
067 status.privacyStatus: unlisted
068 - id: Bazeagj5R-w
069 snippet.title: '"Datenerfassung mit Altgeräten", Linux Magazin 2016-11, Programmier-Snapshot, Michael Schilli'
070 status.privacyStatus: unlisted
071 - id: OaTlu3wwGmo
072 snippet.title: '"Motion Detection mit OpenCV", Linux Magazin 2016-12, Programmier-Snapshot, Michael Schilli'
073 status.privacyStatus: unlisted
074 - id: YcvnpseMm1U
075 snippet.title: '"Stromausfall im Smart Home", Linux Magazin 2017-01, Programmier-Snapshot, Michael Schilli'
076 status.privacyStatus: public
077 - id: lTFt9Otr5V8
078 snippet.title: '"Amazon Web Services (AWS) Lambda (1)", Linux Magazin 2017-02, Programmier-Snapshot, Michael Schilli'
079 status.privacyStatus: unlisted
080 - id: 99afAACicQE
081 snippet.title: '"Amazon Web Services (AWS) Lambda (2)", Linux Magazin 2017-03, Programmier-Snapshot, Michael Schilli'
082 status.privacyStatus: unlisted
083 - id: _Av1gxnQdWE
084 snippet.title: '"Neue Alexa-Skills programmieren", Linux Magazin 2017-04, Programmier-Snapshot, Michael Schilli'
085 status.privacyStatus: unlisted
086 - id: oFyphTrl_Ec
087 snippet.title: '"Textnachricht bei Login-Fehlern", Linux Magazin 2017-05, Programmier-Snapshot, Michael Schilli'
088 status.privacyStatus: unlisted
089 - id: 4iXRBhg8Kvg
090 snippet.title: '"Web-Anfragen mit Scriptsprachen", Linux Magazin 2017-06, Programmier-Snapshot, Michael Schilli'
091 status.privacyStatus: unlisted
092 - id: ldBgrXhiryk
093 snippet.title: '"Tensorflow und Scikit", Linux Magazin 2017-07, Programmier-Snapshot, Michael Schilli'
094 status.privacyStatus: unlisted
095 - id: nvHZ9CuzFRU
096 snippet.title: '"Decision Trees", Linux Magazin 2017-08, Programmier-Snapshot, Michael Schilli'
097 status.privacyStatus: unlisted
098 - id: _3i5yVoTvCs
099 snippet.title: How to flip German pancakes
100 status.privacyStatus: public
101 - id: rqknlsriCRQ
102 snippet.title: La Crosse BC-700 BC-9009 fix charging completely discharged batteries LaCrosse
103 status.privacyStatus: public
104 - id: owPhzBXpqi8
105 snippet.title: Door bell sensor for Smartthings hub
106 status.privacyStatus: public
107 - id: E164UUBY8Hk
108 snippet.title: Pretzel baking at home with Kathi German Pretzel Mix
109 status.privacyStatus: public
110 - id: 2qxXhW7RxsY
111 snippet.title: Rio Portable Beach Shelter Assembly Instructions
112 status.privacyStatus: public
113 - id: brPfE66FC24
114 snippet.title: Tivo Stream Cooling Fan Replacement
115 status.privacyStatus: public
116 - id: SzBmCIaI0LM
117 snippet.title: '"(Ab-) Using Round Robin Databases with Perl", Mike Schilli, at YAPC::NA 2008'
118 status.privacyStatus: public
119 - id: voh0HWnEgXk
120 snippet.title: Surfer unter der Golden Gate Bridge
121 status.privacyStatus: public
122 - id: tuIeKuEB3ac
123 snippet.title: BAPC Artists' Talk, Berkeley, 11-12-2008
124 status.privacyStatus: private
125 - id: yZj-0esWlks
126 snippet.title: Cooking Wiener Schnitzel in San Francisco
127 status.privacyStatus: public
128 - id: MfNBa5aaaeE
129 snippet.title: '"Kursverfall", Linux Magazin 2018-02, Programmier-Snapshot, Michael Schilli'
130 status.privacyStatus: public
131 - id: bvOvnyJTyss
132 snippet.title: 'Scored a goal at Garfield Square!'
133 status.privacyStatus: public
134 - id: ssYpyXjGw9g
135 snippet.title: 'How to fix Acura/Honda not starting in hot weather'
136 status.privacyStatus: public
137 - id: _Cxu3-UP0G8
138 snippet.title: 'GIMP - Scissors Select Tool (Magnetic lasso Photoshop) - Remove thumb in picture'
139 status.privacyStatus: public
140 - id: R_v-2Tjl7dE
141 snippet.title: 'Do Raisins Float In Water?'
142 status.privacyStatus: public
143 - id: FnHkb7sLYas
144 snippet.title: 'Denmantau in Venice Beach'
145 status.privacyStatus: public
146 - id: LtrA4xuEXC4
147 snippet.title: 'A Stormy Day in San Francisco'
148 status.privacyStatus: public
149 - id: LdSTIa2Tx4o
150 snippet.title: 'Flip it: Record messages and play them Backwards'
151 status.privacyStatus: public
152 - id: Oma3xy1j4nY
153 snippet.title: 'Driving a USB Rocket Launcher from Perl in User Space (2/2) (Mike Schilli, YAPC::NA 2009)'
154 status.privacyStatus: public
155 - id: 3px7coM0X4I
156 snippet.title: 'Driving a USB Rocket Launcher from Perl in User Space (1/2) (Mike Schilli, YAPC::NA 2009)'
157 status.privacyStatus: public
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 mittels der 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).
|
| 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]) zunächst ein neues Projekt an (Abbildung 3). Anschließend navigiert er zu "Create Credentials" und wählt "OAuth client ID" aus (nicht "API key", der dient nur zur Projektverwaltung).
|
| Abbildung 3: Neue API-Keys anlegen. |
Da es sich um ein Desktop-Programm und nicht um eine Web-Applikation handelt, ist im Auswahl-Menü zur Applikationsart "Other" auszuwählen. Die anschließend von Google produzierten Strings für "Client ID" und "Client Secret" (Abbildung 4) sind in eine Json-Datei nach Listing 2 einzutragen.
|
| Abbildung 4: Ein neues Projekt namens "prog-snapshot" auf der Cloud Platform Console. |
|
| Abbildung 5: Aktive API-Schlüssel auf der Google-Console. |
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 zukünftigen Aufrufen Zugriff auf die Userdaten gewährt, ohne dass der User erneut einwilligen muss.
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 }
Dies funktioniert so lange, bis der Access-Token ausläuft. Das entsprechende Verfallsdatum ist ebenfalls in der Json-Datei vermerkt. Weiter enthält die Datei einen Refresh-Token, mit dem das Skript nach Ablauf der Gültigkeit des Access-Tokens einen frischen anfordern kann, was praktisch endlos funktioniert, es sei denn, der User begibt sich auf die Google Console und entzieht dem Client den Zugriff, dann dreht Google den Hahn zu.
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-local.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'])
Die Funktion get_authenticated_service() ab Zeile 21 in Listing 3 definiert als "Scope" also als Reichweite des Zugriffs YOUTUBE_READ_WRITE_SCOPE, fordert also Lese- sowie Schreibrechte an. Die Interaktion mit dem Browser und den dahintersteckenden Oauth2-Tokentanz hat Google schön im SDK abstrahiert, das Skript ruft lediglich die Funktionen flow_from_clientsecrets() und run_flow() aus den Paketen oauth2client.client und oauth2client.tools auf. Das Ringelreihen mit dem Browser funkioniert sowohl auf Linux als auch auf dem Mac erstaunlicherweise gleich gut.
Zeile 69 liest die Yaml-Datei mit den lokal gehaltenen Video-Metadaten ein und 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 Videos des Users einholt, und 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 den Titel des Videos und vergleicht ihn mit der lokalen Version. Stimmen beide 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 den lokal gehaltenen im Versionierungssystem.
Das zur Inbetriebnahme des Skripts notwendige SDK ist als Python-Paket im Standard-Repository erhältlich und kann mit pip installiert werden:
$ pip install --upgrade google-api-python-client
Nach dem ersten Lauf des Skripts, das das eingangs gezeigte Browserfenster aufspannt und das Einverständnis des Users einholt, zeigt das Skript mit der Yaml-Datei in Listing 1 die folgende Ausgabe:
$ ./youtube-sync
_3i5yVoTvCs: Unchanged
brPfE66FC24: Unchanged
2qxXhW7RxsY: Unchanged
Da alle Titel-Strings in den frisch eingeholten Youtube-Metadaten mit den Yaml-Daten übereinstimmen, nimmt das Skript keinerlei Anpassungen vor. Ändert sich allerdings ein Titeltext, pumpt das Skript diesen zu Youtube, das die Metadaten des Videos auffrischt. Das Skript bestätigt dies mit "Updated OK".
Das Skript lässt sich nun 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 wie Git, kann er zeitlich vor- und wieder zurückspringen, wagemutig Änderungen vornehmen und sie sofort wieder zurückrollen, falls sich eine Idee mal als nicht so glorreich erweist.
Listings zu diesem Artikel: http://www.linux-magazin.de/static/listings/magazin/2018/01/snapshot/
Michael Schilli, "Titel": Linux-Magazin 12/16, S.104, <U>http://www.linux-magazin.de/Ausgaben/2016/12/Perl-Snapshot<U>
Google Cloud Platform Console: https://console.cloud.google.com/apis
"OAuth 2.0 for Mobile & Desktop Apps", https://developers.google.com/identity/protocols/OAuth2InstalledApp
Hey! The above document had some coding errors, which are explained below:
Unknown directive: =desc