mirror of
https://github.com/MatomoCamp/live-platform.git
synced 2024-09-09 04:33:44 +02:00
peertube api and more data
This commit is contained in:
parent
7e4ac0f245
commit
1ad7f4e529
10 changed files with 497 additions and 45 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,3 +8,4 @@ static/main.css.map
|
|||
venv/
|
||||
runtime/
|
||||
static/tag-manager/files/
|
||||
token.json
|
||||
|
|
2
app.py
2
app.py
|
@ -9,7 +9,7 @@ from utils import get_css
|
|||
|
||||
app = Flask(__name__)
|
||||
|
||||
not_yet_published_message = "The recording for this talk has not yet been published." \
|
||||
not_yet_published_message = "The recording for this talk has not yet been published. " \
|
||||
"Please come back later or follow us on social media to be notified."
|
||||
|
||||
|
||||
|
|
45
data.py
45
data.py
|
@ -3,11 +3,12 @@ from dataclasses import dataclass
|
|||
from datetime import datetime, timedelta, timezone
|
||||
from random import randint, random
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import pytz
|
||||
from dateutil.parser import parse
|
||||
|
||||
from urls import chat_rooms, workshop_urls, recording_ids, archive_names
|
||||
from urls import chat_rooms, workshop_urls, recording_ids, archive_names, recording_ids_drafts
|
||||
from utils import translated_dict_to_string, time_plusminus15min
|
||||
|
||||
STREAM_FALLBACKS = False
|
||||
|
@ -97,17 +98,38 @@ class Talk:
|
|||
def chat_room_id(self) -> str:
|
||||
return f"#{self.chat_room}:matomocamp.org"
|
||||
|
||||
@property
|
||||
def archive_name(self) -> Optional[str]:
|
||||
try:
|
||||
return archive_names[self.id]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def archive_name_encoded(self) -> Optional[str]:
|
||||
try:
|
||||
return quote_plus(archive_names[self.id]).replace("+", "%20")
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def chat_room_url(self) -> str:
|
||||
try:
|
||||
return f"https://archive.matomocamp.org/{self.year}/{archive_names[self.id]}/chat/"
|
||||
return f"https://archive.matomocamp.org/{self.year}/{self.archive_name_encoded}/chat/"
|
||||
except KeyError:
|
||||
return "https://chat.matomocamp.org/#/room/" + self.chat_room_id
|
||||
|
||||
@property
|
||||
def archive_url(self) -> Optional[str]:
|
||||
try:
|
||||
return f"https://archive.matomocamp.org/{self.year}/{archive_names[self.id]}/"
|
||||
return f"https://archive.matomocamp.org/{self.year}/{self.archive_name_encoded}/"
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def subtitle_edit_url(self) -> Optional[str]:
|
||||
try:
|
||||
return f"https://github.com/MatomoCamp/recording-subtitles/tree/main/{self.year}/{self.archive_name_encoded}/"
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
@ -152,6 +174,12 @@ class Talk:
|
|||
return recording_ids[self.id]
|
||||
return None
|
||||
|
||||
@property
|
||||
def recording_id_drafts(self) -> Optional[str]:
|
||||
if self.id in recording_ids_drafts:
|
||||
return recording_ids_drafts[self.id]
|
||||
return None
|
||||
|
||||
@property
|
||||
def recording_url(self) -> Optional[str]:
|
||||
if self.recording_id:
|
||||
|
@ -273,3 +301,14 @@ if __name__ == '__main__':
|
|||
for talk in talks:
|
||||
if talk.id not in chat_rooms:
|
||||
print(f"missing chatroom: {talk.id} ({talk.title})")
|
||||
|
||||
with open("/home/lukas/tmp/stats.json") as f:
|
||||
data = json.load(f)
|
||||
data_dict = {}
|
||||
for e in data:
|
||||
data_dict[e["label"].lstrip("/")] = e
|
||||
for talk in talks:
|
||||
if talk.year != 2022 or talk.id == "389UYH":
|
||||
continue
|
||||
num_pageviews = data_dict[talk.id]["nb_visits"]
|
||||
print(f"{talk.title};{', '.join(talk.speaker_names)};{num_pageviews}")
|
||||
|
|
1
peertube/__init__.py
Normal file
1
peertube/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .api import PeertubeAPI
|
131
peertube/api.py
Normal file
131
peertube/api.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
import inspect
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Tuple, Literal, Dict, List, Union
|
||||
|
||||
from requests import Session
|
||||
|
||||
technology_category = 15
|
||||
license_id_sa = 2
|
||||
|
||||
|
||||
class PeertubeAPI:
|
||||
|
||||
def __init__(self, base_url: str):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.api_url = self.base_url + "/api/v1"
|
||||
self.s = Session()
|
||||
self.s.max_redirects = 0
|
||||
try:
|
||||
with open("token.json") as f:
|
||||
data = json.load(f)
|
||||
self.access_token = data["access_token"]
|
||||
|
||||
except FileNotFoundError:
|
||||
self.access_token = None
|
||||
|
||||
def get_client(self) -> Tuple[str, str]:
|
||||
r = self.s.get(self.api_url + "/oauth-clients/local")
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
return data["client_id"], data["client_secret"]
|
||||
|
||||
def login(self, username: str, password: str):
|
||||
client_id, client_secret = self.get_client()
|
||||
now = datetime.now().timestamp()
|
||||
r = self.s.post(self.api_url + "/users/token", data={
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"grant_type": "password"
|
||||
})
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
data["expires_in_ts"] = now + data["expires_in"]
|
||||
data["refresh_token_expires_in_ts"] = now + data["refresh_token_expires_in"]
|
||||
with open("token.json", "w") as f:
|
||||
json.dump(data, f)
|
||||
|
||||
@property
|
||||
def headers(self):
|
||||
return {
|
||||
"Authorization": f"Bearer {self.access_token}"
|
||||
}
|
||||
|
||||
def get_video(self, id: str) -> "Video":
|
||||
r = self.s.get(self.api_url + f"/videos/{id}")
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
r2 = self.s.get(self.api_url + f"/videos/{id}/description")
|
||||
r2.raise_for_status()
|
||||
data["description"] = r2.json()["description"]
|
||||
|
||||
return Video.from_dict(data, self)
|
||||
|
||||
def get_captions(self, id: str) -> Dict[str, Dict[str, Union[str, int]]]:
|
||||
r = self.s.get(self.api_url + f"/videos/{id}/captions")
|
||||
r.raise_for_status()
|
||||
subtitles = {}
|
||||
for entry in r.json()["data"]:
|
||||
entry["timestamp"] = datetime.fromisoformat(entry["updatedAt"].replace("Z", "+00:00")).timestamp()
|
||||
subtitles[entry["language"]["id"]] = entry
|
||||
|
||||
return subtitles
|
||||
|
||||
def upload_caption(self, id: str, lang: str, caption_file: Path):
|
||||
r = self.s.put(
|
||||
self.api_url + f"/videos/{id}/captions/{lang}",
|
||||
headers=self.headers,
|
||||
files={
|
||||
"captionfile": caption_file.open("rb")
|
||||
}
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
def update_video(self, id: str, data: Dict) -> None:
|
||||
del data["api"]
|
||||
r = self.s.put(self.api_url + f"/videos/{id}", json=data, headers=self.headers)
|
||||
# print(r.json())
|
||||
r.raise_for_status()
|
||||
|
||||
def update_thumbnail(self, id: str, file: Path) -> None:
|
||||
print(file)
|
||||
r = self.s.put(self.api_url + f"/videos/{id}", files={
|
||||
"thumbnailfile": ("thumb.png", file.open("rb"), "image/png")
|
||||
}, headers=self.headers)
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
@dataclass
|
||||
class Video:
|
||||
shortUUID: str
|
||||
api: PeertubeAPI
|
||||
category: int
|
||||
description: str
|
||||
language: str
|
||||
licence: int
|
||||
name: str
|
||||
originallyPublishedAt: str
|
||||
privacy: Literal[1, 2, 3, 4]
|
||||
support: str
|
||||
tags: List[str] = field()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, env, api: PeertubeAPI):
|
||||
"""
|
||||
https://stackoverflow.com/a/55096964/4398037
|
||||
"""
|
||||
bla = {}
|
||||
for k, v in env.items():
|
||||
if k not in inspect.signature(cls).parameters:
|
||||
continue
|
||||
if isinstance(v, dict):
|
||||
v = v["id"]
|
||||
bla[k] = v
|
||||
return cls(api=api, **bla)
|
||||
|
||||
def save(self) -> None:
|
||||
self.api.update_video(self.shortUUID, self.__dict__)
|
80
poetry.lock
generated
80
poetry.lock
generated
|
@ -9,6 +9,25 @@ python-versions = ">=3.6"
|
|||
[package.dependencies]
|
||||
pytz = ">=2015.7"
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.9.24"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "2.1.1"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.0"
|
||||
|
||||
[package.extras]
|
||||
unicode-backport = ["unicodedata2"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
|
@ -64,6 +83,14 @@ gevent = ["gevent (>=1.4.0)"]
|
|||
setproctitle = ["setproctitle"]
|
||||
tornado = ["tornado (>=0.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.4"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "5.0.0"
|
||||
|
@ -140,6 +167,24 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.28.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7, <4"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = ">=2017.4.17"
|
||||
charset-normalizer = ">=2,<3"
|
||||
idna = ">=2.5,<4"
|
||||
urllib3 = ">=1.21.1,<1.27"
|
||||
|
||||
[package.extras]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
|
||||
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
|
||||
|
||||
[[package]]
|
||||
name = "setproctitle"
|
||||
version = "1.3.2"
|
||||
|
@ -172,6 +217,19 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "1.26.12"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "2.2.2"
|
||||
|
@ -201,13 +259,21 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "654a73bf99aa07317e8feb8664836eb1159a6bb9f04ce370c5f8a8c85c0a861a"
|
||||
content-hash = "56c965d24edfb55874c9fccb0cc9c43f1959d69e3a9be1d02fcdf0adf9aa4fd4"
|
||||
|
||||
[metadata.files]
|
||||
babel = [
|
||||
{file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"},
|
||||
{file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
|
||||
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
|
||||
]
|
||||
charset-normalizer = [
|
||||
{file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
|
||||
{file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
|
@ -224,6 +290,10 @@ gunicorn = [
|
|||
{file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
|
||||
{file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"},
|
||||
{file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"},
|
||||
|
@ -298,6 +368,10 @@ pytz = [
|
|||
{file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"},
|
||||
{file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||
]
|
||||
setproctitle = [
|
||||
{file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe"},
|
||||
{file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18"},
|
||||
|
@ -368,6 +442,10 @@ six = [
|
|||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
|
||||
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"},
|
||||
{file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"},
|
||||
|
|
|
@ -12,6 +12,7 @@ Babel = "^2.9.1"
|
|||
libsass = "^0.21.0"
|
||||
gunicorn = "^20.1.0"
|
||||
setproctitle = "^1.2.2"
|
||||
requests = "^2.28.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
|
|
169
urls.py
169
urls.py
|
@ -34,48 +34,46 @@ chat_rooms = {
|
|||
|
||||
# 2022
|
||||
|
||||
"H7YHTG": "event-introduction-2022", # Event introduction by the MatomoCamp organizers
|
||||
"JSPF8U": "multi-server-and-docker", # Yet to be named support talk
|
||||
"PYQY3A": "monter-une-infrastructure-matomo", # Monter une infrastructure Matomo qui gère des millions de hits par mois n’est pas si simple
|
||||
"H7YHTG": "event-introduction-2022",
|
||||
"JSPF8U": "multi-server-and-docker",
|
||||
"PYQY3A": "monter-une-infrastructure-matomo",
|
||||
"3DBQCM": "developing-a-plugin",
|
||||
# How to create your own Matomo plugin? A technical introduction to developing for Matomo.
|
||||
"3LRN3P": "ideal-matomo", # Imagine Ideal Matomo !
|
||||
"EMG79M": "reussir-limplementation-de-matomo", # Réussir l'implémentationde Matomo - SOS Villages d'enfants x Elevate
|
||||
"MWQUT7": "future-of-matomo", # An Inside Look at the Future of Matomo: User Experience + User Connection
|
||||
"S37GBU": "gestion-de-projet-web-analytics", # Gestion de projet Web Analytics avec Matomo
|
||||
"PU7SJR": "contributing-back", # Contribute back
|
||||
"33QBWR": "importance-of-privacy", # Is Matomo only for European companies and authorities?
|
||||
"AXGTWL": "analysez-vos-performances", # Analysez vos performances digitales avec Matomo
|
||||
"SXLAJL": "matomo-in-a-data-stack", # Integrating Matomo in a data stack
|
||||
"R393MD": "external-dashboards-data-visualization", # Matomo: External Dashboards & Data Visualization
|
||||
"SNEUXP": "migration-de-vos-datas-ga-vers-matomo", # Migration de vos datas Google Analytics UA vers Matomo
|
||||
"QCGUJB": "security-hardening", # Linux security hardening on a Matomo installation
|
||||
"ADJLAW": "dashboards-with-apache-superset", # Open source dashboards ontop of Matomo with Apache Superset
|
||||
"PYT37H": "matomo-et-wordpress", # Matomo et WordPress comment les connecter ?
|
||||
"3LRN3P": "ideal-matomo",
|
||||
"EMG79M": "reussir-limplementation-de-matomo",
|
||||
"MWQUT7": "future-of-matomo",
|
||||
"S37GBU": "gestion-de-projet-web-analytics",
|
||||
"PU7SJR": "contributing-back",
|
||||
"33QBWR": "importance-of-privacy",
|
||||
"AXGTWL": "analysez-vos-performances",
|
||||
"SXLAJL": "matomo-in-a-data-stack",
|
||||
"R393MD": "external-dashboards-data-visualization",
|
||||
"SNEUXP": "migration-de-vos-datas-ga-vers-matomo",
|
||||
"QCGUJB": "security-hardening",
|
||||
"ADJLAW": "dashboards-with-apache-superset",
|
||||
"PYT37H": "matomo-et-wordpress",
|
||||
"QB8VY8": "using-kubernetes-and-docker",
|
||||
# Running Matomo with open source technologies such as Kubernetes and docker
|
||||
"XGPBTU": "matomo-and-us-distributors", # Why Wholesale Distributors in the US should be using Matomo
|
||||
"XMGR83": "les-traductions-dans-matomo", # Les traductions dans Matomo, comment ça marche?
|
||||
"XGPBTU": "matomo-and-us-distributors",
|
||||
"XMGR83": "les-traductions-dans-matomo",
|
||||
"UKTQD8": "matomo-su-kubernetes",
|
||||
"EDHADW": "tag-manager-and-consent-solutions", # Tag Manager/Consent solutions
|
||||
"CQ3TRP": "vanilla-analytics", # Matomo & Vanilla Analytics
|
||||
"U9GDMC": "vuejs-in-plugins", # Building Matomo plugins with VueJS.
|
||||
"TPFDWN": "dashboard", # Get a kick ass dashboard in Matomo
|
||||
"SRDLAJ": "das-problem-zu-viel", # Das Problem "zu viel" - Mehr Insights in Matomo
|
||||
"KDWXFH": "intranet-analytics", # Get started with intranet analytics in Matomo
|
||||
"XRVPDS": "das-problem-zu-wenig", # Das Problem "zu wenig" - Mehr Insights in Matomo
|
||||
"ZXGJYA": "improve-seo", # Improve your SEO with Matomo
|
||||
"7YDHL7": "tracking-spas", # Tracking SPA applications with Matomo
|
||||
"97RZGZ": "standortermittlung", # Standortermittlung mit Matomo
|
||||
"FXLH93": "snowplow", # Data Creation and Snowplow Open Source
|
||||
"FUD7JR": "log-analytics", # How to use Matomo Log Analysis to retrieve audience data
|
||||
"BRH8SH": "datenschutzkonformes-performance-marketing", # Datenschutzkonformes Performance Marketing mit Matomo
|
||||
"N3WJCA": "creating-plugins-as-non-developers", # How to create a plugin for Matomo when you are not a developer?
|
||||
"EDHADW": "tag-manager-and-consent-solutions",
|
||||
"CQ3TRP": "vanilla-analytics",
|
||||
"U9GDMC": "vuejs-in-plugins",
|
||||
"TPFDWN": "dashboard",
|
||||
"SRDLAJ": "das-problem-zu-viel",
|
||||
"KDWXFH": "intranet-analytics",
|
||||
"XRVPDS": "das-problem-zu-wenig",
|
||||
"ZXGJYA": "improve-seo",
|
||||
"7YDHL7": "tracking-spas",
|
||||
"97RZGZ": "standortermittlung",
|
||||
"FXLH93": "snowplow",
|
||||
"FUD7JR": "log-analytics",
|
||||
"BRH8SH": "datenschutzkonformes-performance-marketing",
|
||||
"N3WJCA": "creating-plugins-as-non-developers",
|
||||
"YE9MYB": "plausible-analytics",
|
||||
"389UYH": "joomla", # Utilisez Matomo avec Joomla
|
||||
"E893JM": "web-analytics-book", # The big book about web analytics
|
||||
"WXXXXE": "migrating-matomo", # The ultimate guide to migrate to Matomo: 7 best practices
|
||||
"ST38HK": "closing-event-2022", # MatomoCamp closing
|
||||
"389UYH": "joomla",
|
||||
"E893JM": "web-analytics-book",
|
||||
"WXXXXE": "migrating-matomo",
|
||||
"ST38HK": "closing-event-2022",
|
||||
}
|
||||
|
||||
archive_names = {
|
||||
|
@ -108,7 +106,49 @@ archive_names = {
|
|||
"XTF7GX": "Tips and Tricks",
|
||||
"Y9AAMG": "Host your own Instance",
|
||||
# "ZBWZHJ": "cleaninsights-workshop",
|
||||
"QQJ3ZS": "PII"
|
||||
"QQJ3ZS": "PII",
|
||||
|
||||
"UKTQD8": "Matomo su Kubernetes",
|
||||
"H7YHTG": "Opening",
|
||||
"JSPF8U": "Multi-Server and Docker",
|
||||
# "PYQY3A": "monter-une-infrastructure-matomo",
|
||||
"3DBQCM": "Your own plugin",
|
||||
"3LRN3P": "Ideal Matomo",
|
||||
"EMG79M": "Réussir l'implémentation de Matomo",
|
||||
"MWQUT7": "Future of Matomo",
|
||||
# "S37GBU": "gestion-de-projet-web-analytics",
|
||||
# "PU7SJR": "contributing-back",
|
||||
"33QBWR": "Importance of Privacy",
|
||||
# "AXGTWL": "analysez-vos-performances",
|
||||
# "SXLAJL": "matomo-in-a-data-stack",
|
||||
"R393MD": "External Dashboards & Data Visualization",
|
||||
# "SNEUXP": "migration-de-vos-datas-ga-vers-matomo",
|
||||
"QCGUJB": "Security Hardening",
|
||||
# "ADJLAW": "dashboards-with-apache-superset",
|
||||
# "PYT37H": "matomo-et-wordpress",
|
||||
# "QB8VY8": "using-kubernetes-and-docker",
|
||||
# "XGPBTU": "matomo-and-us-distributors",
|
||||
# "XMGR83": "les-traductions-dans-matomo",
|
||||
# "UKTQD8": "matomo-su-kubernetes",
|
||||
"EDHADW": "Tag Manager and Consent solutions",
|
||||
# "CQ3TRP": "vanilla-analytics",
|
||||
"U9GDMC": "VueJS in plugins",
|
||||
"TPFDWN": "Kick-ass dashboard",
|
||||
"SRDLAJ": "Das Problem \"zu viel\"",
|
||||
"KDWXFH": "Intranet Analytics",
|
||||
# "XRVPDS": "das-problem-zu-wenig",
|
||||
"ZXGJYA": "Improve your SEO",
|
||||
# "7YDHL7": "tracking-spas",
|
||||
"97RZGZ": "Standortermittlung",
|
||||
"FXLH93": "Snowplow",
|
||||
"FUD7JR": "Log Analytics",
|
||||
"BRH8SH": "Performance Marketing",
|
||||
"N3WJCA": "Creating plugins as non-developers",
|
||||
"YE9MYB": "Plausible Analytics",
|
||||
# "389UYH": "joomla",
|
||||
"E893JM": "Web Analytics Book",
|
||||
"WXXXXE": "Migrating Matomo",
|
||||
"ST38HK": "Closing",
|
||||
|
||||
}
|
||||
|
||||
|
@ -146,8 +186,57 @@ recording_ids = {
|
|||
"URWXGR": "9cExMatGbi3xH4hjJgndvt",
|
||||
"ENHZCR": "dEYt3WxM5V4jBk6BhT5oab",
|
||||
"GMUXZZ": "7N9bhdSHYq6zvfrt5E8mwN",
|
||||
"8HSWJD": "cBxLoU5N9g23C7QbBFsAf6"
|
||||
"8HSWJD": "cBxLoU5N9g23C7QbBFsAf6",
|
||||
|
||||
"ZXGJYA": "pJ1C6vLGhcJSfw5ZHvc6YA",
|
||||
"YE9MYB": "s8kxkasrJr38x4CiZcfSu6",
|
||||
"R393MD": "a4QnuwVmcDrEnjghxHspYt",
|
||||
"EDHADW": "55yLTdxSPh4ntJZRr5Gf1Q",
|
||||
|
||||
}
|
||||
recording_ids_drafts = {
|
||||
"H7YHTG": "uo31Qr3gSdd4zFh2knTpr5",
|
||||
"JSPF8U": "gbUPYbQSEmpFTn3hgYfuMN",
|
||||
# "PYQY3A": "monter-une-infrastructure-matomo",
|
||||
"3DBQCM": "nK3Hyq8hG1MtQ7zFdSQVpD",
|
||||
"3LRN3P": "pj39SJYfUX9ZVXB6KHoGo7",
|
||||
"EMG79M": "dQsWaFHLCAqnDhMyW34jDW",
|
||||
"MWQUT7": "qVqqbK6ezsJnZcbLg2HzPp",
|
||||
# "S37GBU": "gestion-de-projet-web-analytics",
|
||||
# "PU7SJR": "contributing-back",
|
||||
"33QBWR": "mR4XH2d6pJc1PUSokvgGBp",
|
||||
# "AXGTWL": "analysez-vos-performances",
|
||||
# "SXLAJL": "matomo-in-a-data-stack",
|
||||
# "SNEUXP": "migration-de-vos-datas-ga-vers-matomo",
|
||||
"QCGUJB": "bqUDz21DEBfNB674Hekeu2",
|
||||
# "ADJLAW": "dashboards-with-apache-superset",
|
||||
# "PYT37H": "matomo-et-wordpress",
|
||||
# "QB8VY8": "using-kubernetes-and-docker",
|
||||
# "XGPBTU": "matomo-and-us-distributors",
|
||||
# "XMGR83": "les-traductions-dans-matomo",
|
||||
"UKTQD8": "mmKVHh4uP9dvXCuUX7sKmo",
|
||||
# "CQ3TRP": "vanilla-analytics",
|
||||
"U9GDMC": "kazVeZ6x7Tv13CZuJDQwa1",
|
||||
"TPFDWN": "faxC1Tc6ukrDD7yZUZXaPw",
|
||||
"SRDLAJ": "dFUE4UsdsAxmCtWLsMVdfS",
|
||||
"KDWXFH": "1Xed35oAQsDbPafBosMK1W",
|
||||
# "XRVPDS": "das-problem-zu-wenig",
|
||||
# "7YDHL7": "tracking-spas",
|
||||
"97RZGZ": "tFZqwSVPX6nqeXvJedgzsG",
|
||||
"FXLH93": "jjuoDmucxfLWTtzyfsn21a",
|
||||
"FUD7JR": "cyVg7Z1RJoDJdEJ5o81k65",
|
||||
"BRH8SH": "jbQpLLk2q7MEXqAggbinLA",
|
||||
"N3WJCA": "q2ij3WGyehmkpxxjSqguy1",
|
||||
# "389UYH": "joomla",
|
||||
"E893JM": "5opi9rRkxYLv9Fz2VWUAgA",
|
||||
"WXXXXE": "iWBWDy8DeF3RBSbVZkEnYs",
|
||||
"ST38HK": "jjzQEvcuw5fgsqmjD4d94J",
|
||||
|
||||
}
|
||||
recording_ids_drafts = {**recording_ids_drafts, **recording_ids}
|
||||
|
||||
slides = {
|
||||
"GDGZXF": "https://schedule.matomocamp.org/media/matomocamp-2021/submissions/GDGZXF/resources/MatomoCamp_opening_ceremony_v2_ln8dBhV.pdf"
|
||||
}
|
||||
|
||||
for name in chat_rooms.values():
|
||||
|
|
10
utils.py
10
utils.py
|
@ -1,5 +1,7 @@
|
|||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Dict, Set
|
||||
|
||||
import sass
|
||||
|
@ -48,6 +50,14 @@ def time_plusminus15min(time: datetime) -> Set[datetime]:
|
|||
times.add(time - min15)
|
||||
return times
|
||||
|
||||
def print_diff_call(str1: str, str2: str, title: str) -> None:
|
||||
with NamedTemporaryFile(delete=True, mode="w", suffix=title) as tmp1:
|
||||
with NamedTemporaryFile(delete=True, mode="w", suffix=title) as tmp2:
|
||||
tmp1.write(str1)
|
||||
tmp1.flush()
|
||||
tmp2.write(str2)
|
||||
tmp2.flush()
|
||||
subprocess.run(["git", "--no-pager", "diff", "--color-words=.", tmp1.name, tmp2.name])
|
||||
|
||||
if __name__ == '__main__':
|
||||
save_css()
|
||||
|
|
102
video.py
Normal file
102
video.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
import sys
|
||||
import time
|
||||
from getpass import getpass
|
||||
from pathlib import Path
|
||||
|
||||
from data import talks, Talk
|
||||
from peertube import PeertubeAPI
|
||||
from peertube.api import license_id_sa, technology_category
|
||||
from utils import print_diff_call
|
||||
|
||||
a = PeertubeAPI(base_url="https://video.matomocamp.org")
|
||||
|
||||
projects_dir = Path("/media/ssd/MatomoCamp Recordings/Projects/")
|
||||
|
||||
|
||||
def description_of_talk(talk: Talk):
|
||||
text = talk.description
|
||||
text += "\n\n"
|
||||
text += f"by {', '.join(talk.speaker_names)}\n"
|
||||
text += "\n"
|
||||
text += f"View in Schedule: {talk.schedule_url}\n"
|
||||
text += f"Audio-only: {talk.archive_url}output.opus\n"
|
||||
text += f"High-Quality Video: {talk.archive_url}output.mp4\n"
|
||||
text += f"Help improve the subtitles: {talk.subtitle_edit_url}\n"
|
||||
|
||||
return text
|
||||
|
||||
|
||||
if not a.access_token:
|
||||
username = input("username: ")
|
||||
password = getpass()
|
||||
|
||||
a.login(username, password)
|
||||
|
||||
for talk in talks:
|
||||
if not talk.recording_id_drafts:
|
||||
continue
|
||||
|
||||
if talk.year != 2022:
|
||||
continue
|
||||
time.sleep(1)
|
||||
|
||||
if sys.argv[1] and talk.id!=sys.argv[1]:
|
||||
continue
|
||||
print(talk)
|
||||
|
||||
video = a.get_video(talk.recording_id_drafts)
|
||||
|
||||
project_dir = projects_dir / str(talk.year) / talk.archive_name
|
||||
print(project_dir)
|
||||
assert project_dir.exists()
|
||||
|
||||
subtitles = {}
|
||||
subtitle_en = project_dir / "output.srt"
|
||||
if subtitle_en.exists():
|
||||
subtitles["en"] = subtitle_en
|
||||
for lang in ["de", "en", "it", "fr"]:
|
||||
subtitle_lang = project_dir / f"output.{lang}.srt"
|
||||
if subtitle_lang.exists():
|
||||
subtitles[lang] = subtitle_lang
|
||||
remote_captions = a.get_captions(talk.recording_id_drafts)
|
||||
for lang, subtitle in subtitles.items():
|
||||
if lang not in remote_captions or subtitle.stat().st_mtime > remote_captions[lang]["timestamp"]:
|
||||
print("upload")
|
||||
a.upload_caption(talk.recording_id_drafts, lang, subtitle)
|
||||
|
||||
if talk.year != 2022:
|
||||
continue
|
||||
|
||||
starttime = talk.start.isoformat().replace('+00:00', '.000Z')
|
||||
video.tags = ["MatomoCamp", talk.track]
|
||||
video.language = talk.language
|
||||
video.licence = license_id_sa
|
||||
video.category = technology_category
|
||||
video.originallyPublishedAt = starttime
|
||||
video.name = talk.title
|
||||
if video.privacy == 2:
|
||||
video.name = "Draft - " + video.name
|
||||
if not video.description:
|
||||
a.update_thumbnail(talk.recording_id_drafts, project_dir / "title-page.png")
|
||||
video.description = description_of_talk(talk)
|
||||
|
||||
print_diff_call(video.description, description_of_talk(talk), video.shortUUID)
|
||||
assert talk.year == 2022
|
||||
video.save()
|
||||
time.sleep(.8)
|
||||
|
||||
# assert talk.title == video.name
|
||||
# if video.shortUUID == "gqEzAyzDJz4KLR9gwygVK2":
|
||||
# video.originallyPublishedAt = starttime
|
||||
# video.name = talk.title
|
||||
# video.save()
|
||||
print("\n\n")
|
||||
# video.licence = license_id_sa
|
||||
# video.category = technology_category
|
||||
# if "MatomoCamp" not in video.tags:
|
||||
# video.tags.append("MatomoCamp")
|
||||
# if not video.language:
|
||||
# video.language = "en"
|
||||
# print(video)
|
||||
# video.description = "test"
|
||||
# video.save()
|
Loading…
Reference in a new issue