gri-gus / loremflickr-streamdeck-plugin

Stream Deck Plugin for installing images from LoremFlickr to keys.
Apache License 2.0
4 stars 0 forks source link

Where is the Log file? #1

Open KazukiOmata opened 3 months ago

KazukiOmata commented 3 months ago

According to settings.py, it looks like the log file will be output, Where can i find the exported log file? mac os M1 max Sonoma 14.3.1


PLUGIN_LOGS_DIR_PATH: Path = Path(os.environ["PLUGIN_LOGS_DIR_PATH"])
PLUGIN_NAME: str = os.environ["PLUGIN_NAME"]

LOG_FILE_PATH: Path = PLUGIN_LOGS_DIR_PATH / Path(f"{PLUGIN_NAME}.log")
LOG_LEVEL: int = logging.DEBUG

IMAGE_SIZE = 144
LOREM_FLICKR_URL = "https://loremflickr.com"
KazukiOmata commented 3 months ago

I want to use pyautogui when I have it keydown, but after adding just a few lines, the exclamation mark appears all the time from the icon. The readme says it's because it hasn't finished loading, but I'm wondering if it could be due to some other issue. I would like to read the error log for that.

import requests
from streamdeck_sdk import (
    StreamDeck,
    Action,
    events_received_objs,
    events_sent_objs,
    image_bytes_to_base64,
    logger,
)

import settings

import pyautogui

class LoremFlickrError(Exception):
    pass

def get_image_from_lorem_flickr(
        category: str,
        union_type: str = "or",
        grayscale_flag: bool = False,
        timeout: float = 5,
) -> str:
    url = settings.LOREM_FLICKR_URL
    if grayscale_flag:
        url += "/g"
    url += f"/{settings.IMAGE_SIZE}/{settings.IMAGE_SIZE}/{category}"
    if union_type == "and":
        url += "/all"
    logger.info(f"{url=}")
    response = requests.get(url, stream=True, timeout=timeout)
    if not response.ok:
        raise LoremFlickrError(f"Bad response. Status code: {response.status_code}")
    image_binary = response.content
    image_mime = response.headers["Content-Type"]
    image_base64 = image_bytes_to_base64(obj=image_binary, image_mime=image_mime)
    return image_base64

class SetKeyImage(Action):
    UUID = "com.ggusev.loremflickr.setkeyimage"

    def on_key_down(self, obj: events_received_objs.KeyDown):
        screenWidth, screenHeight = pyautogui.size()
        category = obj.payload.settings.get("category", screenWidth)
        union_type = obj.payload.settings.get("union_type", "or")
        grayscale_flag = obj.payload.settings.get("grayscale_flag", False)

        logger.info(f"{category=}, {union_type=}, {grayscale_flag=}")
        try:
            image_base64 = get_image_from_lorem_flickr(
                category=category,
                union_type=union_type,
                grayscale_flag=grayscale_flag,
            )
        except Exception as err:  # noqa
            logger.error(str(err))
            self.show_alert(context=obj.context)
            return

        self.set_image(
            context=obj.context,
            payload=events_sent_objs.SetImagePayload(
                image=image_base64,
                target=0,
                state=obj.payload.state,
            )
        )

if __name__ == '__main__':
    StreamDeck(
        actions=[
            SetKeyImage(),
        ],
        log_file=settings.LOG_FILE_PATH,
        log_level=settings.LOG_LEVEL,
        log_backup_count=1,
    ).run()

    print("hello")
gri-gus commented 3 months ago

@KazukiOmata Hello, the log files is located in the directory of the plugin installed in Stream Deck. Example for Mac OS: /Users/gri-gus/Library/Application Support/com.elgato.StreamDeck/Plugins/com.ggusev.loremflickr.sdPlugin/logs/

init.log: log of the initialization of the project (creating venv, installing dependencies, etc.). com.ggusev.loremflickr.sdPlugin.log: log of the plugin and the libraries that are used in it. If this file is missing, then most likely the initialization has not yet completed or there are errors indicated in the log.

I also advise you to double-check whether you have added pyautogui and its dependencies to the file requirements.txt

KazukiOmata commented 3 months ago

@gri-gus Hello, thank you for answering. I found the log file init.log. maybe, com.ggusev.loremflickr.sdPlugin.log was not generated yet.

I checked the contents of venv and noticed that pyatuogui was not pip installed. I fixed requirements.txt and it works fine.

thank you very much

KazukiOmata commented 3 months ago

@gri-gus Am I correct that init.py is only called the first time I load the plugin?

and, Is it possible to turn this python into an executable file? I would like to be able to use the streamdeck plugin on machines that do not have python natively installed, such as M1 macs.

gri-gus commented 3 months ago

@KazukiOmata

How does it work?

Stream Deck does not have the ability to run Python files, but it does have the ability to run .bat and .sh files.

But it all starts with the manifest.json file, which contains "CodePathMac": "run.sh" and "CodePathWin": "run.bat". These are scripts that are run depending on the system.

Files run.sh for MacOS or run.bat for Windows: startup scripts, entry points. They set environment variables, check whether Python is installed (if not, an error window pops up), and run the init.py script. Based on the result from init.py, if everything is fine, then the main.py file is launched and the plugin is launched, and if not, then an error window pops up. This happens every time you restart the Stream Deck application and when you reinstall/update the plugin.

What is the init.py file? This is a script that is responsible for the virtual environment and dependencies. Why can't we immediately add venv to the built version of the plugin? Because a user with a system/hardware different from the one on which venv was made may encounter compatibility problems. Therefore, for Python, everyone should have their own virtual environment for each project. The init.py file is responsible for creating the virtual environment, installing dependencies from requirements.txt, and checking that everything is installed correctly. It also runs every time you restart the Stream Deck application and when you reinstall/update the plugin. But if the virtual environment has already been created and the dependencies are installed, then init.py simply checks that everything is installed correctly.

Later, the main.py file comes into play, which contains the plugin logic.

Answers on questions.

Am I correct that init.py is only called the first time I load the plugin?

Not entirely true. It is called every time the Stream Deck application is restarted and when the plugin is reinstalled/updated, but if the virtual environment has already been created and the dependencies are installed, then init.py simply checks that everything is installed correctly.

Is it possible to turn this Python into an executable file? I would like to be able to use the streamdeck plugin on machines that do not have Python natively installed, such as M1 macs.

In theory it is possible, but I have not done this. In this case, if everything works out, you will need to specify it in manifest.json and build the plugin. But other users may encounter Python libs compatibility issues.

KazukiOmata commented 3 months ago

Hello

if main.py call send_to_property_inspector() like below main.py


        self.send_to_property_inspector(
            action=self.UUID,
            context=obj.context,
            payload=_payload
        )

How Property inspector does receive payload data? your sample project doesn't contain like that code?

gri-gus commented 3 months ago

@KazukiOmata

How to use documentation if it is not written?

Let's look at an example of how the self.send_to_property_inspector method works.

Let's look at the documentation from Elgato: Here is the object sent from the plugin when calling self.send_to_property_inspector: click Here is the resulting object in the Property inspector when calling self.send_to_property_inspector: click

Here is the method source code for the self.send_to_property_inspector method:

def send_to_property_inspector(
         self,
         action: str,
         context: str,
         payload: dict
):
     message = events_sent_objs.SendToPropertyInspector(
         action=action,
         context=context,
         payload=payload
     )
     self.send(message)

As we can see, it accepts function parameters and transfers them to the object events_sent_objs.SendToPropertyInspector:

class SendToPropertyInspector(BaseModel):
     action: str
     context: str
     payload: dict
     event: str = "sendToPropertyInspector"

Next in the method self.send the pydantic object is converted to json and sent to Property Inspector.

What is payload?

It's any dict you want. But there is a condition, it must be convertible to json.

Answers on questions.

How Property inspector does receive payload data?

To answer this question, you need to look at the source code streamdeck-javascript-sdk. As I understand, in their sdk there is a method onSendToPropertyInspector and most likely it should be used like this:

$PI.onSendToPropertyInspector("com.ggusev.keyboard.write", jsn => {
     payload = jsn.payload; // I'm not sure about this, you need to test it
     ...
});

Instead of "com.ggusev.keyboard.write" you need to substitute the name of your action.