From 7a46dfe9b8b5712124d9f344c13b48cb89389314 Mon Sep 17 00:00:00 2001 From: bartool Date: Fri, 29 Aug 2025 06:45:55 +0200 Subject: [PATCH] proof of concept --- 550d_config.py | 51 ++++++++++++++++++++ config.txt | 82 ++++++++++++++++++++++++++++++++ live_flask.py | 29 ++++++++++++ live_pyside.py | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ live_view.py | 26 ++++++++++ test_pyside.py | 27 +++++++++++ 6 files changed, 340 insertions(+) create mode 100644 550d_config.py create mode 100644 config.txt create mode 100644 live_flask.py create mode 100644 live_pyside.py create mode 100644 live_view.py create mode 100644 test_pyside.py diff --git a/550d_config.py b/550d_config.py new file mode 100644 index 0000000..038889e --- /dev/null +++ b/550d_config.py @@ -0,0 +1,51 @@ +import gphoto2 as gp + +# def list_config(widget, indent=0): +# name = widget.get_name() +# label = widget.get_label() +# type_name = widget.get_type() +# choices = widget.get_choices() +# if choices: +# choices_str = ", ".join(choices) +# print(" " * indent + f"{name} ({label}) type={type_name} choices=[{choices_str}]") +# else: + +# print(" " * indent + f"{name} ({label}) type={type_name}") + +# for i in range(widget.count_children()): +# child = widget.get_child(i) +# list_config(child, indent + 1) + + +def list_config(widget, indent=0): + name = widget.get_name() + label = widget.get_label() + type_name = widget.get_type() + + line = " " * indent + f"{name} ({label}) type={type_name}" + + # tylko jeśli widget obsługuje choices + try: + choices = widget.get_choices() + if choices: + choices_str = ", ".join(str(c) for c in choices) + line += f" choices=[{choices_str}]" + except gp.GPhoto2Error: + # brak choices -> ignorujemy + pass + + print(line) + + # rekurencja dla dzieci + for i in range(widget.count_children()): + child = widget.get_child(i) + list_config(child, indent + 1) + + +camera = gp.Camera() +camera.init() + +config = camera.get_config() +list_config(config) + +camera.exit() diff --git a/config.txt b/config.txt new file mode 100644 index 0000000..6807c9a --- /dev/null +++ b/config.txt @@ -0,0 +1,82 @@ +(.venv) bartool@BARTOOL-PC:~/local_projects/canon_550d$ python 550d_config.py +main (Camera and Driver Configuration) type=0 + actions (Camera Actions) type=1 + syncdatetime (Synchronize camera date and time with PC) type=4 + uilock (UI Lock) type=4 + popupflash (Popup Flash) type=4 + autofocusdrive (Drive Canon DSLR Autofocus) type=4 + manualfocusdrive (Drive Canon DSLR Manual focus) type=5 choices=[Near 1, Near 2, Near 3, None, Far 1, Far 2, Far 3] + cancelautofocus (Cancel Canon DSLR Autofocus) type=4 + eoszoom (Canon EOS Zoom) type=2 + eoszoomposition (Canon EOS Zoom Position) type=2 + viewfinder (Canon EOS Viewfinder) type=4 + eosremoterelease (Canon EOS Remote Release) type=5 choices=[None, Press Half, Press Full, Release Half, Release Full, Immediate, Press 1, Press 2, Press 3, Release 1, Release 2, Release 3] + eosmoviemode (Movie Mode) type=4 + opcode (PTP Opcode) type=2 + settings (Camera Settings) type=1 + datetime (Camera Date and Time) type=8 + reviewtime (Quick Review Time) type=5 choices=[None, 2 seconds, 4 seconds, 8 seconds, Hold] + output (Camera Output) type=5 choices=[TFT, PC, MOBILE, Off] + movierecordtarget (Recording Destination) type=5 choices=[None] + evfmode (EVF Mode) type=5 choices=[0, 1] + ownername (Owner Name) type=2 + artist (Artist) type=2 + copyright (Copyright) type=2 + customfuncex (Custom Functions Ex) type=2 + focusinfo (Focus Info) type=2 + strobofiring (Strobo Firing) type=5 choices=[0, 1, 2] + flashcharged (Flash Charging State) type=2 + autopoweroff (Auto Power Off) type=2 + depthoffield (Depth of Field) type=2 + capturetarget (Capture Target) type=5 choices=[Internal RAM, Memory card] + capture (Capture) type=4 + remotemode (Remote Mode) type=2 + eventmode (Event Mode) type=2 + status (Camera Status Information) type=1 + serialnumber (Serial Number) type=2 + manufacturer (Camera Manufacturer) type=2 + cameramodel (Camera Model) type=2 + deviceversion (Device Version) type=2 + vendorextension (Vendor Extension) type=2 + model (Camera Model) type=2 + ptpversion (PTP Version) type=2 + batterylevel (Battery Level) type=2 + batterylevel (Battery Level) type=2 + mirrorlockstatus (Mirror Lock Status) type=2 + mirrordownstatus (Mirror Down Status) type=2 + lensname (Lens Name) type=2 + eosserialnumber (Serial Number) type=2 + shuttercounter (Shutter Counter) type=2 + availableshots (Available Shots) type=2 + eosmovieswitch (Movie Switch) type=2 + imgsettings (Image Settings) type=1 + imageformat (Image Format) type=5 choices=[L, cL, M, cM, S, cS, RAW + L, RAW] + imageformatsd (Image Format SD) type=5 choices=[L, cL, M, cM, S, cS, RAW + L, RAW] + iso (ISO Speed) type=5 choices=[Auto, 100, 200, 400, 800, 1600, 3200, 6400] + whitebalance (WhiteBalance) type=5 choices=[Auto, Daylight, Shadow, Cloudy, Tungsten, Fluorescent, Flash, Manual] + whitebalanceadjusta (WhiteBalance Adjust A) type=5 choices=[-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + whitebalanceadjustb (WhiteBalance Adjust B) type=5 choices=[-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + whitebalancexa (WhiteBalance X A) type=5 choices=[0, 1, 2, 3] + whitebalancexb (WhiteBalance X B) type=5 choices=[0, 1, 2, 3] + colorspace (Color Space) type=5 choices=[sRGB, AdobeRGB] + capturesettings (Capture Settings) type=1 + exposurecompensation (Exposure Compensation) type=5 choices=[-5, -4.6, -4.3, -4, -3.6, -3.3, -3, -2.6, -2.3, -2, -1.6, -1.3, -1, -0.6, -0.3, 0, 0.3, 0.6, 1, 1.3, 1.6, 2, 2.3, 2.6, 3, 3.3, 3.6, 4, 4.3, 4.6, 5] + focusmode (Focus Mode) type=5 choices=[One Shot, AI Focus, AI Servo, Manual] + afmethod (AF Method) type=5 choices=[Live, LiveFace, Quick] + storageid (Storage Device) type=2 + autoexposuremode (Canon Auto Exposure Mode) type=5 choices=[P, TV, AV, Manual, Bulb, A_DEP, DEP, Custom, Lock, Green, Night Portrait, Sports, Portrait, Landscape, Closeup, Flash Off, C2, C3, Creative Auto, Movie, Auto, Handheld Night Scene, HDR Backlight Control, SCN, Food, Grainy B/W, Soft focus, Toy camera effect, Fish-eye effect, Water painting effect, Miniature effect, HDR art standard, HDR art vivid, HDR art bold, HDR art embossed, Panning, HDR, Self Portrait, Hybrid Auto, Smooth skin, Fv] + drivemode (Drive Mode) type=5 choices=[Single, Continuous, Timer 10 sec, Timer 2 sec, Continuous timer] + picturestyle (Picture Style) type=5 choices=[Standard, Portrait, Landscape, Neutral, Faithful, Monochrome, User defined 1, User defined 2, User defined 3] + aperture (Aperture) type=5 choices=[implicit auto] + shutterspeed (Shutter Speed) type=5 choices=[30, 25, 20, 15, 13, 10.3, 8, 6.3, 5, 4, 3.2, 2.5, 2, 1.6, 1.3, 1, 0.8, 0.6, 0.5, 0.4, 0.3, 1/4, 1/5, 1/6, 1/8, 1/10, 1/13, 1/15, 1/20, 1/25, 1/30, 1/40, 1/50, 1/60, 1/80, 1/100, 1/125, 1/160, 1/200, 1/250, 1/320, 1/400, 1/500, 1/640, 1/800, 1/1000, 1/1250, 1/1600, 1/2000, 1/2500, 1/3200, 1/4000] + meteringmode (Metering Mode) type=5 choices=[Evaluative, Partial, Spot, Center-weighted average] + liveviewsize (Live View Size) type=5 choices=[Large, Medium] + bracketmode (Bracket Mode) type=5 choices=[Unknown value 0000] + aeb (Auto Exposure Bracketing) type=5 choices=[off, +/- 1/3, +/- 2/3, +/- 1, +/- 1 1/3, +/- 1 2/3, +/- 2] + alomode (Auto Lighting Optimization) type=5 choices=[Standard (disabled in manual exposure), x1, x2, x3] + other (Other PTP Device Properties) type=1 + d402 (Friendly Device Name) type=2 + d407 (Perceived Device Type) type=2 + d406 (Session Initiator Info) type=2 + d303 (PTP Property 0xd303) type=2 + 5001 (Battery Level) type=6 choices=[100, 0, 75, 50] \ No newline at end of file diff --git a/live_flask.py b/live_flask.py new file mode 100644 index 0000000..a2ad596 --- /dev/null +++ b/live_flask.py @@ -0,0 +1,29 @@ +import cv2 +import numpy as np +import gphoto2 as gp +from flask import Flask, Response + +app = Flask(__name__) + +camera = gp.Camera() +camera.init() + +def generate(): + while True: + file = camera.capture_preview() + data = file.get_data_and_size() + frame = np.frombuffer(data, dtype=np.uint8) + frame = cv2.imdecode(frame, cv2.IMREAD_COLOR) + + if frame is not None: + _, buffer = cv2.imencode('.jpg', frame) + yield (b'--frame\r\n' + b'Content-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n') + +@app.route('/liveview') +def liveview(): + return Response(generate(), + mimetype='multipart/x-mixed-replace; boundary=frame') + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) diff --git a/live_pyside.py b/live_pyside.py new file mode 100644 index 0000000..3d05035 --- /dev/null +++ b/live_pyside.py @@ -0,0 +1,125 @@ +import sys +import cv2 +import numpy as np +from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout +from PySide6.QtGui import QImage, QPixmap +from PySide6.QtCore import QTimer +import gphoto2 as gp + +class LiveViewApp(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Canon LiveView") + + # Widget do obrazu + self.image_label = QLabel("Brak obrazu") + self.image_label.setFixedSize(640, 480) + + # Przyciski start/stop + self.start_button = QPushButton("Start LiveView") + self.stop_button = QPushButton("Stop LiveView") + self.stop_button.setEnabled(False) + + self.start_button.clicked.connect(self.start_liveview) + self.stop_button.clicked.connect(self.stop_liveview) + + # Layout + button_layout = QHBoxLayout() + button_layout.addWidget(self.start_button) + button_layout.addWidget(self.stop_button) + + layout = QVBoxLayout() + layout.addWidget(self.image_label) + layout.addLayout(button_layout) + + self.setLayout(layout) + + # Timer do odświeżania obrazu + self.timer = QTimer() + self.timer.timeout.connect(self.update_frame) + + # kamera (na razie None – podepniesz gphoto2) + self.camera = None + self.set_dummy_frame() + + + + def set_dummy_frame(self): + self.dummy_frame = np.zeros((480, 640, 3), dtype=np.uint8) + cv2.putText(self.dummy_frame, "LiveView OFF", (200, 240), + cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + + rgb_image = cv2.cvtColor(self.dummy_frame, cv2.COLOR_BGR2RGB) + h, w, ch = rgb_image.shape + qimg = QImage(rgb_image.data, w, h, ch * w, QImage.Format_RGB888) + pixmap = QPixmap.fromImage(qimg) + + self.image_label.setPixmap(pixmap) + + def start_liveview(self): + print("Start LiveView") + self.start_button.setEnabled(False) + self.stop_button.setEnabled(True) + # TODO: tu zainicjalizujesz kamerę gphoto2 + # Przykład inicjalizacji kamery przez gphoto2 (wymaga zainstalowanego python-gphoto2) + try: + self.camera = gp.Camera() + self.camera.init() + except Exception as e: + print(f"Błąd inicjalizacji kamery: {e}") + self.camera = None + self.timer.start(100) # odświeżanie co 100ms + + def stop_liveview(self): + print("Stop LiveView") + self.timer.stop() + self.start_button.setEnabled(True) + self.stop_button.setEnabled(False) + self.image_label.setText("Brak obrazu") + self.image_label.setPixmap(QPixmap()) # czyści obraz + + self.set_dummy_frame() + # TODO: tu zamkniesz kamerę gphoto2 + if self.camera: + self.camera.exit() + self.camera = None + self.start_button.setEnabled(True) + self.stop_button.setEnabled(False) + + def update_frame(self): + # Na razie sztuczna klatka testowa z OpenCV (czarne tło + napis) + # frame = np.zeros((480, 640, 3), dtype=np.uint8) + # cv2.putText(frame, "Canon LiveView", (50, 240), + # cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + + if self.camera: + try: + file = self.camera.capture_preview() + data = file.get_data_and_size() + frame = np.frombuffer(data, dtype=np.uint8) + frame = cv2.imdecode(frame, cv2.IMREAD_COLOR) + + if frame is None: + return + + except gp.GPhoto2Error as e: + print(f"Błąd odczytu LiveView: {e}") + return + except Exception as e: + print(f"Nieoczekiwany błąd: {e}") + return + else: + return + # Konwersja OpenCV -> QImage + rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + h, w, ch = rgb_image.shape + qimg = QImage(rgb_image.data, w, h, ch * w, QImage.Format_RGB888) + pixmap = QPixmap.fromImage(qimg) + + self.image_label.setPixmap(pixmap) + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = LiveViewApp() + window.show() + sys.exit(app.exec()) diff --git a/live_view.py b/live_view.py new file mode 100644 index 0000000..c27c81d --- /dev/null +++ b/live_view.py @@ -0,0 +1,26 @@ +import gphoto2 as gp +import cv2 +import numpy as np + +camera = gp.Camera() +camera.init() + +def liveview(): + while True: + # Pobierz klatkę z LiveView + file = camera.capture_preview() + data = file.get_data_and_size() + frame = np.frombuffer(data, dtype=np.uint8) + frame = cv2.imdecode(frame, cv2.IMREAD_COLOR) + + if frame is not None: + cv2.imshow("LiveView", frame) + + if cv2.waitKey(1) == 27: # ESC + break + + cv2.destroyAllWindows() + camera.exit() + +if __name__ == "__main__": + liveview() diff --git a/test_pyside.py b/test_pyside.py new file mode 100644 index 0000000..bb95051 --- /dev/null +++ b/test_pyside.py @@ -0,0 +1,27 @@ +import sys +from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout + +class TestWindow(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("PySide6 Test") + + # Tworzymy label + self.label = QLabel("Hello from PySide6!", self) + + # Tworzymy przycisk do zamykania + self.close_button = QPushButton("Zamknij") + self.close_button.clicked.connect(self.close) + + # Układ pionowy + layout = QVBoxLayout() + layout.addWidget(self.label) + layout.addWidget(self.close_button) + + self.setLayout(layout) + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = TestWindow() + window.show() + sys.exit(app.exec())