pibooth-pcloud 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christophe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.rst
3
+ include pibooth.cfg.example
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: pibooth_pcloud
3
+ Version: 1.0.0
4
+ Summary: Pibooth plugin to upload photos to pCloud and display a QR code
5
+ Home-page: https://github.com/ceeeeb/pibooth-pcloud
6
+ Download-URL: https://github.com/ceeeeb/pibooth-pcloud/archive/1.0.0.tar.gz
7
+ Author: Christophe
8
+ License: MIT license
9
+ Keywords: Raspberry Pi,camera,photobooth,pcloud
10
+ Platform: unix
11
+ Platform: linux
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Other Environment
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3.6
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Natural Language :: English
22
+ Classifier: Topic :: Multimedia :: Graphics :: Capture :: Digital Camera
23
+ Description-Content-Type: text/x-rst
24
+ License-File: LICENSE
25
+ Requires-Dist: pibooth>=2.0.0
26
+ Requires-Dist: qrcode>=6.1
27
+ Requires-Dist: requests
28
+ Dynamic: author
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: download-url
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: platform
38
+ Dynamic: requires-dist
39
+ Dynamic: summary
40
+
41
+ ===============
42
+ pibooth-pcloud
43
+ ===============
44
+
45
+ |PythonVersions| |License|
46
+
47
+ Plugin ``pibooth-pcloud`` pour `pibooth <https://github.com/pibooth/pibooth>`_
48
+ qui envoie automatiquement les photos finales vers un compte
49
+ `pCloud <https://www.pcloud.com>`_ et affiche un QR code vers la galerie
50
+ publique sur l'écran d'attente.
51
+
52
+ Fonctionnalités :
53
+
54
+ - Authentification par email + mot de passe (digest par appel, aucun OAuth à
55
+ créer côté pCloud).
56
+ - Upload d'un album par événement (``folder_path/album_name``) et lien public
57
+ généré sur ce sous-dossier.
58
+ - Synchronisation de rattrapage : au démarrage et après chaque photo, les
59
+ fichiers présents en local mais absents de pCloud sont envoyés, ce qui
60
+ absorbe les coupures Internet pendant un événement.
61
+
62
+ .. note:: Une connexion internet est requise pendant l'événement (ou plus
63
+ tard pour rattraper les uploads).
64
+
65
+ Installation
66
+ ------------
67
+
68
+ ::
69
+
70
+ pip install pibooth-pcloud
71
+
72
+ Configuration
73
+ -------------
74
+
75
+ Vérifier les identifiants pCloud
76
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
77
+
78
+ Le script fourni valide les identifiants et imprime le bloc à coller dans
79
+ ``~/.config/pibooth/pibooth.cfg`` :
80
+
81
+ ::
82
+
83
+ pibooth-pcloud-token
84
+
85
+ L'email doit être vérifié côté pCloud — sans vérification, l'API refuse la
86
+ création de liens publics.
87
+
88
+ Exemple de configuration
89
+ ~~~~~~~~~~~~~~~~~~~~~~~~
90
+
91
+ Voir ``pibooth.cfg.example`` à la racine du dépôt. Bloc à ajouter dans
92
+ ``~/.config/pibooth/pibooth.cfg`` :
93
+
94
+ .. code-block:: ini
95
+
96
+ [PCLOUD]
97
+
98
+ # Activer le plugin
99
+ activate = True
100
+
101
+ # Identifiants pCloud (stockés en clair — restreindre les droits du fichier)
102
+ email = your.email@example.com
103
+ password = your-password
104
+
105
+ # Région du compte (EU ou US)
106
+ region = EU
107
+
108
+ # Dossier parent sur pCloud (créé s'il n'existe pas)
109
+ folder_path = /Pibooth
110
+
111
+ # Sous-dossier événement sur pCloud ; le lien public cible ce sous-dossier
112
+ album_name = MonEvenement
113
+
114
+ # Position du QR code (top-left, top-right, bottom-left, bottom-right, center)
115
+ qr_position = top-right
116
+
117
+ # Taille du QR code (3-10)
118
+ qr_size = 5
119
+
120
+ # Marge du QR code par rapport au bord (pixels)
121
+ qr_margin = 10
122
+
123
+ Après édition, restreindre les droits du fichier :
124
+
125
+ ::
126
+
127
+ chmod 600 ~/.config/pibooth/pibooth.cfg
128
+
129
+ Fonctionnement
130
+ --------------
131
+
132
+ Au démarrage de pibooth :
133
+
134
+ - Authentification par digest, création du dossier parent et de l'album.
135
+ - Récupération du lien public de l'album, génération du QR code.
136
+ - Première passe de synchronisation : les photos locales absentes de pCloud
137
+ sont envoyées.
138
+
139
+ Après chaque photo (``state_processing_exit``) :
140
+
141
+ - Synchronisation en arrière-plan : la nouvelle photo et toute photo
142
+ restée en retard sont envoyées. L'opération ne bloque pas l'interface.
143
+
144
+ .. |PythonVersions| image:: https://img.shields.io/badge/python-3.6+-green.svg
145
+ :target: https://www.python.org/downloads/
146
+
147
+ .. |License| image:: https://img.shields.io/badge/license-MIT-blue.svg
148
+ :target: https://opensource.org/licenses/MIT
@@ -0,0 +1,108 @@
1
+ ===============
2
+ pibooth-pcloud
3
+ ===============
4
+
5
+ |PythonVersions| |License|
6
+
7
+ Plugin ``pibooth-pcloud`` pour `pibooth <https://github.com/pibooth/pibooth>`_
8
+ qui envoie automatiquement les photos finales vers un compte
9
+ `pCloud <https://www.pcloud.com>`_ et affiche un QR code vers la galerie
10
+ publique sur l'écran d'attente.
11
+
12
+ Fonctionnalités :
13
+
14
+ - Authentification par email + mot de passe (digest par appel, aucun OAuth à
15
+ créer côté pCloud).
16
+ - Upload d'un album par événement (``folder_path/album_name``) et lien public
17
+ généré sur ce sous-dossier.
18
+ - Synchronisation de rattrapage : au démarrage et après chaque photo, les
19
+ fichiers présents en local mais absents de pCloud sont envoyés, ce qui
20
+ absorbe les coupures Internet pendant un événement.
21
+
22
+ .. note:: Une connexion internet est requise pendant l'événement (ou plus
23
+ tard pour rattraper les uploads).
24
+
25
+ Installation
26
+ ------------
27
+
28
+ ::
29
+
30
+ pip install pibooth-pcloud
31
+
32
+ Configuration
33
+ -------------
34
+
35
+ Vérifier les identifiants pCloud
36
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
37
+
38
+ Le script fourni valide les identifiants et imprime le bloc à coller dans
39
+ ``~/.config/pibooth/pibooth.cfg`` :
40
+
41
+ ::
42
+
43
+ pibooth-pcloud-token
44
+
45
+ L'email doit être vérifié côté pCloud — sans vérification, l'API refuse la
46
+ création de liens publics.
47
+
48
+ Exemple de configuration
49
+ ~~~~~~~~~~~~~~~~~~~~~~~~
50
+
51
+ Voir ``pibooth.cfg.example`` à la racine du dépôt. Bloc à ajouter dans
52
+ ``~/.config/pibooth/pibooth.cfg`` :
53
+
54
+ .. code-block:: ini
55
+
56
+ [PCLOUD]
57
+
58
+ # Activer le plugin
59
+ activate = True
60
+
61
+ # Identifiants pCloud (stockés en clair — restreindre les droits du fichier)
62
+ email = your.email@example.com
63
+ password = your-password
64
+
65
+ # Région du compte (EU ou US)
66
+ region = EU
67
+
68
+ # Dossier parent sur pCloud (créé s'il n'existe pas)
69
+ folder_path = /Pibooth
70
+
71
+ # Sous-dossier événement sur pCloud ; le lien public cible ce sous-dossier
72
+ album_name = MonEvenement
73
+
74
+ # Position du QR code (top-left, top-right, bottom-left, bottom-right, center)
75
+ qr_position = top-right
76
+
77
+ # Taille du QR code (3-10)
78
+ qr_size = 5
79
+
80
+ # Marge du QR code par rapport au bord (pixels)
81
+ qr_margin = 10
82
+
83
+ Après édition, restreindre les droits du fichier :
84
+
85
+ ::
86
+
87
+ chmod 600 ~/.config/pibooth/pibooth.cfg
88
+
89
+ Fonctionnement
90
+ --------------
91
+
92
+ Au démarrage de pibooth :
93
+
94
+ - Authentification par digest, création du dossier parent et de l'album.
95
+ - Récupération du lien public de l'album, génération du QR code.
96
+ - Première passe de synchronisation : les photos locales absentes de pCloud
97
+ sont envoyées.
98
+
99
+ Après chaque photo (``state_processing_exit``) :
100
+
101
+ - Synchronisation en arrière-plan : la nouvelle photo et toute photo
102
+ restée en retard sont envoyées. L'opération ne bloque pas l'interface.
103
+
104
+ .. |PythonVersions| image:: https://img.shields.io/badge/python-3.6+-green.svg
105
+ :target: https://www.python.org/downloads/
106
+
107
+ .. |License| image:: https://img.shields.io/badge/license-MIT-blue.svg
108
+ :target: https://opensource.org/licenses/MIT
@@ -0,0 +1,28 @@
1
+ [PCLOUD]
2
+
3
+ # Enable upload to pCloud
4
+ activate = True
5
+
6
+ # pCloud account email (must be verified on pCloud's side)
7
+ email = your.email@example.com
8
+
9
+ # pCloud account password (stored in clear, run: chmod 600 pibooth.cfg)
10
+ password = your-password
11
+
12
+ # Account region: EU or US
13
+ region = EU
14
+
15
+ # Remote parent folder on pCloud (created if it does not exist)
16
+ folder_path = /Pibooth
17
+
18
+ # Event sub-folder inside folder_path; the public link points here
19
+ album_name = MyEvent
20
+
21
+ # QR code position: top-left, top-right, bottom-left, bottom-right, center
22
+ qr_position = top-right
23
+
24
+ # QR code box_size (3-10)
25
+ qr_size = 5
26
+
27
+ # QR code margin from screen edge in pixels
28
+ qr_margin = 10
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: pibooth_pcloud
3
+ Version: 1.0.0
4
+ Summary: Pibooth plugin to upload photos to pCloud and display a QR code
5
+ Home-page: https://github.com/ceeeeb/pibooth-pcloud
6
+ Download-URL: https://github.com/ceeeeb/pibooth-pcloud/archive/1.0.0.tar.gz
7
+ Author: Christophe
8
+ License: MIT license
9
+ Keywords: Raspberry Pi,camera,photobooth,pcloud
10
+ Platform: unix
11
+ Platform: linux
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Other Environment
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3.6
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Natural Language :: English
22
+ Classifier: Topic :: Multimedia :: Graphics :: Capture :: Digital Camera
23
+ Description-Content-Type: text/x-rst
24
+ License-File: LICENSE
25
+ Requires-Dist: pibooth>=2.0.0
26
+ Requires-Dist: qrcode>=6.1
27
+ Requires-Dist: requests
28
+ Dynamic: author
29
+ Dynamic: classifier
30
+ Dynamic: description
31
+ Dynamic: description-content-type
32
+ Dynamic: download-url
33
+ Dynamic: home-page
34
+ Dynamic: keywords
35
+ Dynamic: license
36
+ Dynamic: license-file
37
+ Dynamic: platform
38
+ Dynamic: requires-dist
39
+ Dynamic: summary
40
+
41
+ ===============
42
+ pibooth-pcloud
43
+ ===============
44
+
45
+ |PythonVersions| |License|
46
+
47
+ Plugin ``pibooth-pcloud`` pour `pibooth <https://github.com/pibooth/pibooth>`_
48
+ qui envoie automatiquement les photos finales vers un compte
49
+ `pCloud <https://www.pcloud.com>`_ et affiche un QR code vers la galerie
50
+ publique sur l'écran d'attente.
51
+
52
+ Fonctionnalités :
53
+
54
+ - Authentification par email + mot de passe (digest par appel, aucun OAuth à
55
+ créer côté pCloud).
56
+ - Upload d'un album par événement (``folder_path/album_name``) et lien public
57
+ généré sur ce sous-dossier.
58
+ - Synchronisation de rattrapage : au démarrage et après chaque photo, les
59
+ fichiers présents en local mais absents de pCloud sont envoyés, ce qui
60
+ absorbe les coupures Internet pendant un événement.
61
+
62
+ .. note:: Une connexion internet est requise pendant l'événement (ou plus
63
+ tard pour rattraper les uploads).
64
+
65
+ Installation
66
+ ------------
67
+
68
+ ::
69
+
70
+ pip install pibooth-pcloud
71
+
72
+ Configuration
73
+ -------------
74
+
75
+ Vérifier les identifiants pCloud
76
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
77
+
78
+ Le script fourni valide les identifiants et imprime le bloc à coller dans
79
+ ``~/.config/pibooth/pibooth.cfg`` :
80
+
81
+ ::
82
+
83
+ pibooth-pcloud-token
84
+
85
+ L'email doit être vérifié côté pCloud — sans vérification, l'API refuse la
86
+ création de liens publics.
87
+
88
+ Exemple de configuration
89
+ ~~~~~~~~~~~~~~~~~~~~~~~~
90
+
91
+ Voir ``pibooth.cfg.example`` à la racine du dépôt. Bloc à ajouter dans
92
+ ``~/.config/pibooth/pibooth.cfg`` :
93
+
94
+ .. code-block:: ini
95
+
96
+ [PCLOUD]
97
+
98
+ # Activer le plugin
99
+ activate = True
100
+
101
+ # Identifiants pCloud (stockés en clair — restreindre les droits du fichier)
102
+ email = your.email@example.com
103
+ password = your-password
104
+
105
+ # Région du compte (EU ou US)
106
+ region = EU
107
+
108
+ # Dossier parent sur pCloud (créé s'il n'existe pas)
109
+ folder_path = /Pibooth
110
+
111
+ # Sous-dossier événement sur pCloud ; le lien public cible ce sous-dossier
112
+ album_name = MonEvenement
113
+
114
+ # Position du QR code (top-left, top-right, bottom-left, bottom-right, center)
115
+ qr_position = top-right
116
+
117
+ # Taille du QR code (3-10)
118
+ qr_size = 5
119
+
120
+ # Marge du QR code par rapport au bord (pixels)
121
+ qr_margin = 10
122
+
123
+ Après édition, restreindre les droits du fichier :
124
+
125
+ ::
126
+
127
+ chmod 600 ~/.config/pibooth/pibooth.cfg
128
+
129
+ Fonctionnement
130
+ --------------
131
+
132
+ Au démarrage de pibooth :
133
+
134
+ - Authentification par digest, création du dossier parent et de l'album.
135
+ - Récupération du lien public de l'album, génération du QR code.
136
+ - Première passe de synchronisation : les photos locales absentes de pCloud
137
+ sont envoyées.
138
+
139
+ Après chaque photo (``state_processing_exit``) :
140
+
141
+ - Synchronisation en arrière-plan : la nouvelle photo et toute photo
142
+ restée en retard sont envoyées. L'opération ne bloque pas l'interface.
143
+
144
+ .. |PythonVersions| image:: https://img.shields.io/badge/python-3.6+-green.svg
145
+ :target: https://www.python.org/downloads/
146
+
147
+ .. |License| image:: https://img.shields.io/badge/license-MIT-blue.svg
148
+ :target: https://opensource.org/licenses/MIT
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.rst
4
+ pibooth.cfg.example
5
+ pibooth_pcloud.py
6
+ pibooth_pcloud_token.py
7
+ setup.py
8
+ pibooth_pcloud.egg-info/PKG-INFO
9
+ pibooth_pcloud.egg-info/SOURCES.txt
10
+ pibooth_pcloud.egg-info/dependency_links.txt
11
+ pibooth_pcloud.egg-info/entry_points.txt
12
+ pibooth_pcloud.egg-info/not-zip-safe
13
+ pibooth_pcloud.egg-info/requires.txt
14
+ pibooth_pcloud.egg-info/top_level.txt
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ pibooth-pcloud-token = pibooth_pcloud_token:main
3
+
4
+ [pibooth]
5
+ pibooth_pcloud = pibooth_pcloud
@@ -0,0 +1,3 @@
1
+ pibooth>=2.0.0
2
+ qrcode>=6.1
3
+ requests
@@ -0,0 +1,2 @@
1
+ pibooth_pcloud
2
+ pibooth_pcloud_token
@@ -0,0 +1,349 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Pibooth plugin for pCloud upload."""
4
+
5
+ import glob
6
+ import hashlib
7
+ import os
8
+ import threading
9
+
10
+ import requests
11
+ import qrcode
12
+ import pygame
13
+
14
+ import pibooth
15
+ from pibooth.utils import LOGGER
16
+
17
+ __version__ = "1.0.0"
18
+
19
+ SECTION = "PCLOUD"
20
+
21
+ # pCloud API hosts per region
22
+ API_HOSTS = {
23
+ "US": "https://api.pcloud.com",
24
+ "EU": "https://eapi.pcloud.com",
25
+ }
26
+
27
+
28
+ def _api_url(host, method):
29
+ return f"{host}/{method}"
30
+
31
+
32
+ ###########################################################################
33
+ # HOOKS pibooth
34
+ ###########################################################################
35
+
36
+
37
+ @pibooth.hookimpl
38
+ def pibooth_configure(cfg):
39
+ """Declare the plugin configuration options."""
40
+ cfg.add_option(SECTION, 'activate', True,
41
+ "Enable upload to pCloud",
42
+ "Enable upload", ['True', 'False'])
43
+ cfg.add_option(SECTION, 'email', '',
44
+ "pCloud account email",
45
+ "Email", '')
46
+ cfg.add_option(SECTION, 'password', '',
47
+ "pCloud account password (stored in clear, chmod 600 recommended)",
48
+ "Password", '')
49
+ cfg.add_option(SECTION, 'region', 'EU',
50
+ "pCloud account region (US or EU)",
51
+ "Region", ['US', 'EU'])
52
+ cfg.add_option(SECTION, 'folder_path', '/Pibooth',
53
+ "Remote parent folder on pCloud",
54
+ "Folder Path", '/Pibooth')
55
+ cfg.add_option(SECTION, 'album_name', 'Pibooth',
56
+ "Sub-folder (event album) inside folder_path; the public link points here",
57
+ "Album Name", 'Pibooth')
58
+ cfg.add_option(SECTION, 'qr_position', 'top-left',
59
+ "QR code position (top-left, top-right, bottom-left, bottom-right, center)",
60
+ "QR Position", ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'center'])
61
+ cfg.add_option(SECTION, 'qr_size', 5,
62
+ "QR code box_size (3-10)",
63
+ "QR Size", '5')
64
+ cfg.add_option(SECTION, 'qr_margin', 10,
65
+ "QR code margin from screen edge in pixels",
66
+ "QR Margin", '10')
67
+
68
+
69
+ @pibooth.hookimpl
70
+ def pibooth_startup(app, cfg):
71
+ """Initialize PCloudUpload instance and create QR code."""
72
+ activate = cfg.getboolean(SECTION, 'activate')
73
+ if not activate:
74
+ LOGGER.info("pCloud upload is disabled")
75
+ app.pcloud = None
76
+ return
77
+
78
+ email = cfg.get(SECTION, 'email').strip()
79
+ password = cfg.get(SECTION, 'password')
80
+ if not email or not password:
81
+ LOGGER.warning("pCloud email or password is not configured")
82
+ app.pcloud = None
83
+ return
84
+
85
+ region = cfg.get(SECTION, 'region').upper()
86
+ api_host = API_HOSTS.get(region, API_HOSTS["EU"])
87
+ parent_path = cfg.get(SECTION, 'folder_path').strip().rstrip('/') or '/Pibooth'
88
+ if not parent_path.startswith('/'):
89
+ parent_path = '/' + parent_path
90
+ album_name = cfg.get(SECTION, 'album_name').strip() or 'Pibooth'
91
+ album_path = f"{parent_path}/{album_name}"
92
+
93
+ app.pcloud = PCloudUpload(email, password, api_host)
94
+ app.pcloud.qr_position = cfg.get(SECTION, 'qr_position')
95
+ app.pcloud.qr_size = cfg.getint(SECTION, 'qr_size')
96
+ app.pcloud.qr_margin = cfg.getint(SECTION, 'qr_margin')
97
+ app.pcloud.local_rep = cfg.get('GENERAL', 'directory')
98
+
99
+ if not app.pcloud.check_credentials():
100
+ LOGGER.error("pCloud authentication failed, plugin disabled")
101
+ app.pcloud = None
102
+ return
103
+
104
+ if app.pcloud.ensure_folder(parent_path) is None:
105
+ LOGGER.error("Cannot create pCloud folder '%s', plugin disabled", parent_path)
106
+ app.pcloud = None
107
+ return
108
+
109
+ album_id = app.pcloud.ensure_folder(album_path)
110
+ if album_id is None:
111
+ LOGGER.error("Cannot create pCloud folder '%s', plugin disabled", album_path)
112
+ app.pcloud = None
113
+ return
114
+
115
+ app.pcloud.folder_path = album_path
116
+ app.pcloud.folder_id = album_id
117
+
118
+ gallery_link = app.pcloud.get_folder_public_link(album_id)
119
+ if not gallery_link:
120
+ LOGGER.warning("Could not create public link, QR code will show folder path")
121
+ gallery_link = album_path
122
+
123
+ app.pcloud.gallery_link = gallery_link
124
+
125
+ # Generate QR code
126
+ qr_box_size = max(3, min(10, app.pcloud.qr_size))
127
+ qr = qrcode.QRCode(
128
+ version=1,
129
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
130
+ box_size=qr_box_size,
131
+ border=2,
132
+ )
133
+ qr.add_data(gallery_link)
134
+ qr.make(fit=True)
135
+ image = qr.make_image(fill_color="black", back_color="white").convert('RGB')
136
+ image.save(os.path.join(app.pcloud.local_rep, 'QRCODE.png'), "PNG")
137
+ app.pcloud.qr_image = pygame.image.fromstring(
138
+ image.tobytes(), image.size, image.mode
139
+ )
140
+
141
+ LOGGER.info("pCloud plugin ready (region=%s, folder=%s)", region, album_path)
142
+
143
+ # Catch up anything left over from previous sessions (e.g. offline uploads).
144
+ _sync_in_background(app)
145
+
146
+
147
+ @pibooth.hookimpl
148
+ def state_wait_enter(cfg, app, win):
149
+ """Display QR code on the wait screen."""
150
+ if not getattr(app, 'pcloud', None):
151
+ return
152
+
153
+ win_rect = win.get_rect()
154
+ qr_rect = app.pcloud.qr_image.get_rect()
155
+ margin = app.pcloud.qr_margin
156
+ position = app.pcloud.qr_position
157
+
158
+ positions = {
159
+ "top-left": (margin, margin),
160
+ "top-right": (win_rect.width - qr_rect.width - margin, margin),
161
+ "bottom-left": (margin, win_rect.height - qr_rect.height - margin),
162
+ "bottom-right": (win_rect.width - qr_rect.width - margin,
163
+ win_rect.height - qr_rect.height - margin),
164
+ "center": ((win_rect.width - qr_rect.width) // 2,
165
+ (win_rect.height - qr_rect.height) // 2),
166
+ }
167
+ x, y = positions.get(position, (margin, margin))
168
+ win.surface.blit(app.pcloud.qr_image, (x, y))
169
+
170
+
171
+ PICTURE_PATTERN = "*_pibooth.jpg"
172
+
173
+
174
+ def _sync_in_background(app):
175
+ """Run a catch-up sync in a daemon thread, guarded by the upload lock."""
176
+ def _do_sync():
177
+ if not app.pcloud.upload_lock.acquire(blocking=False):
178
+ LOGGER.debug("pCloud sync already in progress, skipping")
179
+ return
180
+ try:
181
+ app.pcloud.sync_missing(
182
+ app.pcloud.local_rep, PICTURE_PATTERN,
183
+ app.pcloud.folder_path, app.pcloud.folder_id,
184
+ )
185
+ finally:
186
+ app.pcloud.upload_lock.release()
187
+
188
+ threading.Thread(target=_do_sync, daemon=True).start()
189
+
190
+
191
+ @pibooth.hookimpl
192
+ def state_processing_exit(app, cfg):
193
+ """Upload captured photo and catch up any missing ones (non-blocking)."""
194
+ if not getattr(app, 'pcloud', None):
195
+ return
196
+ _sync_in_background(app)
197
+
198
+
199
+ ###########################################################################
200
+ # pCloud API client
201
+ ###########################################################################
202
+
203
+
204
+ class PCloudUpload:
205
+
206
+ DIGEST_TTL = 60 # seconds — pCloud digests are short-lived, refresh often
207
+
208
+ def __init__(self, email, password, api_host):
209
+ self.email = email
210
+ self.password = password
211
+ self.api_host = api_host
212
+ self.upload_lock = threading.Lock()
213
+ self._digest_lock = threading.Lock()
214
+ self._username_sha1 = hashlib.sha1(email.lower().encode()).hexdigest()
215
+ self.gallery_link = ""
216
+ self.folder_path = ""
217
+ self.folder_id = 0
218
+ self.qr_image = None
219
+ self.qr_position = "top-left"
220
+ self.qr_size = 5
221
+ self.qr_margin = 10
222
+ self.local_rep = ""
223
+
224
+ def _fresh_auth_params(self):
225
+ """Fetch a new digest and compute the password digest for a single call."""
226
+ with self._digest_lock:
227
+ resp = requests.get(_api_url(self.api_host, "getdigest"), timeout=30)
228
+ resp.raise_for_status()
229
+ data = resp.json()
230
+ if data.get("result") != 0:
231
+ raise RuntimeError(f"getdigest failed: {data.get('error')}")
232
+ digest = data["digest"]
233
+ password_digest = hashlib.sha1(
234
+ (self.password + self._username_sha1 + digest).encode()
235
+ ).hexdigest()
236
+ return {"username": self.email, "digest": digest,
237
+ "passworddigest": password_digest}
238
+
239
+ def _call(self, method, params=None, files=None, timeout=30):
240
+ """Call a pCloud API method authenticated with a fresh digest."""
241
+ url = _api_url(self.api_host, method)
242
+ params = dict(params or {})
243
+
244
+ try:
245
+ params.update(self._fresh_auth_params())
246
+ if files:
247
+ resp = requests.post(url, data=params, files=files, timeout=timeout)
248
+ else:
249
+ resp = requests.get(url, params=params, timeout=timeout)
250
+ resp.raise_for_status()
251
+ data = resp.json()
252
+ if data.get("result") != 0:
253
+ LOGGER.error("pCloud API error %s: %s",
254
+ data.get("result"), data.get("error", "unknown"))
255
+ return None
256
+ return data
257
+ except (requests.RequestException, RuntimeError) as e:
258
+ LOGGER.error("pCloud request failed (%s): %s", method, e)
259
+ return None
260
+
261
+ def check_credentials(self):
262
+ """Verify the email/password authenticate successfully."""
263
+ data = self._call("userinfo")
264
+ if data:
265
+ LOGGER.info("pCloud authenticated as %s", data.get("email", "unknown"))
266
+ return True
267
+ return False
268
+
269
+ def ensure_folder(self, path):
270
+ """Create the folder (and parents) if it does not exist.
271
+ Returns the folder ID or None on error.
272
+ """
273
+ data = self._call("createfolderifnotexists", {"path": path})
274
+ if data and "metadata" in data:
275
+ folder_id = data["metadata"]["folderid"]
276
+ LOGGER.info("pCloud folder ready: %s (id=%s)", path, folder_id)
277
+ return folder_id
278
+ return None
279
+
280
+ def list_folder_filenames(self, folder_id):
281
+ """Return the set of file names present in a remote folder."""
282
+ data = self._call("listfolder", {"folderid": folder_id})
283
+ if not data or "metadata" not in data:
284
+ return None
285
+ return {entry["name"] for entry in data["metadata"].get("contents", [])
286
+ if not entry.get("isfolder")}
287
+
288
+ def sync_missing(self, local_dir, pattern, remote_folder_path, folder_id):
289
+ """Upload local files matching pattern that are absent on pCloud.
290
+
291
+ Returns the number of files successfully uploaded.
292
+ """
293
+ if not os.path.isdir(local_dir):
294
+ return 0
295
+
296
+ remote_names = self.list_folder_filenames(folder_id)
297
+ if remote_names is None:
298
+ LOGGER.warning("pCloud sync: cannot list remote folder, skipping")
299
+ return 0
300
+
301
+ uploaded = 0
302
+ for local_path in sorted(glob.glob(os.path.join(local_dir, pattern))):
303
+ if os.path.basename(local_path) in remote_names:
304
+ continue
305
+ if self.upload_file(local_path, remote_folder_path):
306
+ uploaded += 1
307
+ if uploaded:
308
+ LOGGER.info("pCloud sync: %d missing file(s) uploaded", uploaded)
309
+ return uploaded
310
+
311
+ def get_folder_public_link(self, folder_id):
312
+ """Get or create a public link for the folder."""
313
+ data = self._call("getfolderpublink", {"folderid": folder_id})
314
+ if data and "link" in data:
315
+ LOGGER.info("pCloud public link: %s", data["link"])
316
+ return data["link"]
317
+ # Try listing existing public links
318
+ data = self._call("listpublinks")
319
+ if data and "publinks" in data:
320
+ for link in data["publinks"]:
321
+ if link.get("folderid") == folder_id:
322
+ return link.get("link", "")
323
+ LOGGER.warning("Could not get public link for folder %s", folder_id)
324
+ return ""
325
+
326
+ def upload_file(self, local_path, remote_folder_path):
327
+ """Upload a single file to the given pCloud folder."""
328
+ filename = os.path.basename(local_path)
329
+ LOGGER.info("Uploading %s to pCloud:%s", filename, remote_folder_path)
330
+
331
+ try:
332
+ with open(local_path, 'rb') as f:
333
+ data = self._call(
334
+ "uploadfile",
335
+ params={"path": remote_folder_path, "filename": filename,
336
+ "nopartial": 1, "renameifexists": 1},
337
+ files={"file": (filename, f)},
338
+ timeout=120,
339
+ )
340
+ except OSError as e:
341
+ LOGGER.error("Cannot read file %s: %s", local_path, e)
342
+ return False
343
+
344
+ if data:
345
+ LOGGER.info("Photo uploaded to pCloud successfully: %s", filename)
346
+ return True
347
+
348
+ LOGGER.error("Upload to pCloud failed for %s", filename)
349
+ return False
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """Validate pCloud email/password and print the pibooth config block.
5
+
6
+ pibooth-pcloud authenticates each API call with a fresh digest derived from
7
+ email+password — no long-lived token. This helper just confirms the
8
+ credentials work and formats the config block ready to paste into
9
+ ~/.config/pibooth/pibooth.cfg.
10
+ """
11
+
12
+ import getpass
13
+ import hashlib
14
+
15
+ import requests
16
+
17
+ API_HOSTS = {
18
+ "EU": "https://eapi.pcloud.com",
19
+ "US": "https://api.pcloud.com",
20
+ }
21
+
22
+
23
+ def main():
24
+ print("=" * 60)
25
+ print(" pibooth-pcloud - Verification des identifiants")
26
+ print("=" * 60)
27
+ print()
28
+
29
+ region = input("Region du compte (EU/US) [EU] : ").strip().upper() or "EU"
30
+ if region not in API_HOSTS:
31
+ print(f"Region invalide : {region}")
32
+ return
33
+ host = API_HOSTS[region]
34
+
35
+ email = input("Email pCloud : ").strip()
36
+ password = getpass.getpass("Mot de passe pCloud : ")
37
+ if not email or not password:
38
+ print("Erreur : email et mot de passe requis.")
39
+ return
40
+
41
+ digest_resp = requests.get(f"{host}/getdigest", timeout=30).json()
42
+ if digest_resp.get("result") != 0:
43
+ print(f"Erreur getdigest : {digest_resp.get('error')}")
44
+ return
45
+ digest = digest_resp["digest"]
46
+
47
+ username_sha1 = hashlib.sha1(email.lower().encode()).hexdigest()
48
+ password_digest = hashlib.sha1(
49
+ (password + username_sha1 + digest).encode()
50
+ ).hexdigest()
51
+
52
+ data = requests.get(f"{host}/userinfo", params={
53
+ "username": email, "digest": digest,
54
+ "passworddigest": password_digest,
55
+ }, timeout=30).json()
56
+
57
+ if data.get("result") != 0:
58
+ print(f"Echec : {data.get('error', 'code ' + str(data.get('result')))}")
59
+ return
60
+
61
+ print()
62
+ print("=" * 60)
63
+ print(" AUTHENTIFICATION REUSSIE")
64
+ print("=" * 60)
65
+ print()
66
+ print(f" Compte : {data.get('email', email)}")
67
+ print(f" Region : {region}")
68
+ print(f" Plan : {data.get('plan', '?')}")
69
+ print()
70
+ print("A ajouter dans ~/.config/pibooth/pibooth.cfg :")
71
+ print()
72
+ print(" [PCLOUD]")
73
+ print(" activate = True")
74
+ print(f" email = {email}")
75
+ print(f" password = {password}")
76
+ print(f" region = {region}")
77
+ print(" folder_path = /Pibooth")
78
+ print(" qr_position = top-right")
79
+ print()
80
+ print("Puis restreindre les droits :")
81
+ print(" chmod 600 ~/.config/pibooth/pibooth.cfg")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ main()
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import re
5
+ from io import open
6
+ import os.path as osp
7
+ from setuptools import setup
8
+
9
+
10
+ HERE = osp.abspath(osp.dirname(__file__))
11
+
12
+ with open(osp.join(HERE, 'pibooth_pcloud.py'), encoding='utf-8') as f:
13
+ content = f.read()
14
+ version = re.search(r'__version__\s*=\s*["\']([^"\']+)["\']', content).group(1)
15
+
16
+
17
+ def main():
18
+ setup(
19
+ name='pibooth_pcloud',
20
+ version=version,
21
+ description="Pibooth plugin to upload photos to pCloud and display a QR code",
22
+ long_description=open(osp.join(HERE, 'README.rst'), encoding='utf-8').read(),
23
+ long_description_content_type='text/x-rst',
24
+ classifiers=[
25
+ 'Development Status :: 4 - Beta',
26
+ 'Environment :: Other Environment',
27
+ 'Intended Audience :: Developers',
28
+ 'Intended Audience :: End Users/Desktop',
29
+ 'License :: OSI Approved :: MIT License',
30
+ 'Operating System :: POSIX :: Linux',
31
+ 'Programming Language :: Python :: 3.6',
32
+ 'Programming Language :: Python :: 3.9',
33
+ 'Programming Language :: Python :: 3.11',
34
+ 'Natural Language :: English',
35
+ 'Topic :: Multimedia :: Graphics :: Capture :: Digital Camera',
36
+ ],
37
+ author="Christophe",
38
+ url="https://github.com/ceeeeb/pibooth-pcloud",
39
+ download_url="https://github.com/ceeeeb/pibooth-pcloud/archive/{}.tar.gz".format(version),
40
+ license='MIT license',
41
+ platforms=['unix', 'linux'],
42
+ keywords=[
43
+ 'Raspberry Pi',
44
+ 'camera',
45
+ 'photobooth',
46
+ 'pcloud',
47
+ ],
48
+ py_modules=['pibooth_pcloud', 'pibooth_pcloud_token'],
49
+ install_requires=[
50
+ 'pibooth>=2.0.0',
51
+ 'qrcode>=6.1',
52
+ 'requests',
53
+ ],
54
+ options={
55
+ 'bdist_wheel': {'universal': True},
56
+ },
57
+ zip_safe=False,
58
+ entry_points={
59
+ 'pibooth': ["pibooth_pcloud = pibooth_pcloud"],
60
+ 'console_scripts': [
61
+ "pibooth-pcloud-token = pibooth_pcloud_token:main",
62
+ ],
63
+ },
64
+ )
65
+
66
+
67
+ if __name__ == '__main__':
68
+ main()