Automatisierung des Tipp Prozesses

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
pmqtt
User
Beiträge: 4
Registriert: Freitag 4. Dezember 2020, 09:00

Hallo Community,
ich brauchte eine kleines Tool, das mir bei der Erstellung von Videos und animierten Gifs helfen kann.
Anhand eines Drehbuches soll eine Folge von Ereignissen durchgeführt werden. Vor allem automatisiert das Tool das Tippen von Text, als Beispiel:
  • 1. Öffne Terminal
    2. Tippe vi Enter
    3. Tippe text
    4. Schliesse vi
Somit kann ich die Anzahl der Wiederholungen, um z.B. ein animiertes Gif zu erzeugen, deutlich reduzieren.


Der Code steht unter https://github.com/pmqtt/IntelliType zur verfügung.
Des Weiteren kann Sie auch per pip install IntelliType installiert werden

Für Kommentare und Anregungen bin ich offen!
Grüße,
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Module und Pakete werden wie Variablennamen oder Funktionen komplett klein geschrieben. Du scheinst eine Klasse pro Modul zu haben. Das macht man in Python nicht, denn so ist der Namespace "Modul" total nutzlos. Die Klassen sind meist so kurz, dass man die meisten davon in ein Modul packen kann.
Nach einem Komma kommt immer ein Leerzeichen, das macht den Code deutlich lesbarer. Benutze keine Abkürzungen, Cmd, Ctrl? Vokale kosten nichts.
Einige Typannotationen sind absolut unsinnig. In Keyspeed z.B. sieht man ja schon am Defaultvalue, oder dass self.speed ein String ist, dass das Strings sein müssen.
Statt langer if-Ketten benutzt man Wörterbücher:

Code: Alles auswählen

import random

SPEED_TYPES = {
    'FAST': lambda: 0.01,
    'SLOW': lambda: 1,
    'MEDIUM': lambda: 0.2,
    'HUMAN': 0.15 + random.uniform(-0.15, 0),
}

class Keyspeed:
    def __init__(self, speed='FAST'):
        self.speed = speed

    def get_speed(self):
        try:
            return SPEED_TYPES[self.speed.upper()]()
        except KeyError:
            return Time(self.speed).get_time_in_seconds()
eigentlich ist die Klasse gar nicht nötig, weil get_speed auch einfach eine Funktion sein könnte.
Genauso ist Time keine Klasse:

Code: Alles auswählen


import random

SPEED_TYPES = {
    'FAST': lambda: 0.01,
    'SLOW': lambda: 1,
    'MEDIUM': lambda: 0.2,
    'HUMAN': 0.15 + random.uniform(-0.15, 0),
}

TIME_UNITS = {
    'ms': 0.001,
    's': 1,
    'm': 60,
    'min': 60,
    'h': 3600,
}

def str_to_seconds(value):
    try:
        for unit, scale in TIME_UNITS.items():
            if value.endswith(unit):
                return int(value[:-len(unit)]) * scale
        raise ValueError()
    except ValueError:
        print(f"Not valid time format {value}! Fallback to notime")
        return 0

def get_speed(speed='FAST'):
    try:
        return SPEED_TYPES[speed.upper()]()
    except KeyError:
        return str_to_seconds(speed)
In Program: Weder _thread noch os.system sollte man verwenden. Unabhängig vom System solltest Du subprocess.run verwenden und Threads baut man mit threading.
Mit subprocess.Popen brauchst Du gar keinen Thread, weil das externe Programm eh parallel läuft.

In StoryScript hast Du ein Problem mit zu restriktiven Typannotations. filename sollte ein pathlib.Path sein dürfen, was ja im Code kein Problem ist, aber mit Deiner Annotation. sys.exit darf nur im Hauptprogramm vorkommen. Wenn Du an der Stelle keine sinnvolle Fehlerbehandlung durchführen kannst, dann mußt Du die Exception nach oben fallen lassen. script_file darf kein Attribut sein und Dateien öffnet man am besten innerhalb von with, damit sie auch wieder geschlossen werden. YAML-Dateien haben normalerweise UTF8-Encoding, das Du beim öffnen angeben mußt. Da eine Instanz der Klasse ohne parse -Aufruf nicht vollständig ist, sollte parse gleich in __init__ aufgerufen werden. `l` ist der schlechteste Variablennamen, den es gibt, weil man ihn leicht mit 1 verwechseln kann. Benutze aussagekräftige Namen.
Das mit dem Keyspeed verstehe ich nicht. Warum bilden die einen Stack?

In __init__ gibst Du zwar bei weniger als einem Argument eine Fehlermeldung aus, bei mehr als einem werden aber die restlichen Argumente kommentarlos ignoriert. `x` ist ein schlechter Name für eine `section`.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das kann man gut alles in eine Datei packen, so groß ist das Programm ja nicht:

Code: Alles auswählen

import sys
import time
import random
import keyboard
import subprocess
import yaml

SPEED_TYPES = {
    'FAST': lambda: 0.01,
    'SLOW': lambda: 1,
    'MEDIUM': lambda: 0.2,
    'HUMAN': lambda: 0.15 + random.uniform(-0.15, 0),
}

TIME_UNITS = {
    'ms': 0.001,
    's': 1,
    'm': 60,
    'min': 60,
    'h': 3600,
}

def str_to_seconds(value):
    try:
        for unit, scale in TIME_UNITS.items():
            if value.endswith(unit):
                return float(value[:-len(unit)]) * scale
        raise ValueError()
    except ValueError:
        print(f"Not valid time format {value}! Fallback to notime")
        return 0

def get_speed(speed='FAST'):
    try:
        return SPEED_TYPES[speed.upper()]()
    except KeyError:
        return str_to_seconds(speed)


class Command:
    def __init__(self, command:str, keyspeed:float):
        self.command = command + '\n'
        self.keyspeed = keyspeed

    def execute(self):
        keyboard.write(self.command, self.keyspeed)


class Control:
    def __init__(self, control:str):
        self.control = control

    def execute(self):
        keyboard.press_and_release(self.control)
        

class Text:
    def __init__(self, text:str, keyspeed:float):
        self.text = text
        self.keyspeed = keyspeed

    def execute(self):
        for character in self.text:
            keyboard.write(character, self.keyspeed)


class Wait:
    def __init__(self, delay:float):
        self.delay = delay

    def execute(self):
        time.sleep(self.delay)


class Program:
    def __init__(self, command:str):
        self.command = command
        self.process = None

    def execute(self):
        self.process = subprocess.Popen(command)


class Scene:
    def __init__(self, program:Program, delay, countdown):
        self.program = program
        self.delay = delay
        self.countdown = countdown

    def run(self, next_szenario=None):
        if self.countdown is not None:
            print(' Program starts in' )
            for i in range(self.countdown):
                print(self.countdown - i)
                time.sleep(1)
        elif self.delay is not None:
            time.sleep(self.delay)
        self.program.execute()


class Section:
    def __init__(self, section_name:str):
        self.section_name = section_name
        self.items = []

    def add(self,item):
        self.items.append(item)

    def run(self):
        for item in self.items:
            item.execute()


class StoryScript:
    def __init__(self, filename):
        self.scene = None
        self.sections = []
        with open(filename, encoding="utf8") as input:
            file_content = yaml.safe_load(input)
        self.parse(file_content)

    def run(self):
        self.scene.run()
        for section in self.sections:
            section.run()

    def parse(self, file_content):
        for key, value in file_content.items():
            if key == 'scene':
                self.parse_scene(value)
            elif key == 'sections':
                self.parse_sections(value)
            else:
                print(f"Unsupported key {key} in root level was found! Will be ignored")

    def parse_scene(self, scene:dict):
        command = None
        delay = None
        countdown = None
        for key, value in scene.items():
            if key == 'program':
                command = value
            elif key == 'wait':
                delay = str_to_seconds(value)
            elif key == 'countdown':
                countdown = int(value)
            else:
                print(f"Unsupported key {key} in scene level was found! Will be ignored")
        self.scene = Scene.Scene(Program(command), delay, countdown)

    def parse_sections(self, sections:dict):
        for section_name, items in sections.items():
            section = Section.Section(section_name)
            for process_items in items:
                values = process_items.items()
                speed = get_speed()
                for key,value in values:
                    if key.upper() == "WAIT":
                        section.add(Wait(str_to_seconds(value)))
                    elif key.upper() == "KEYSPEED" :
                        speed = get_speed(value)
                    elif key.upper() == "TEXT":
                        section.add(Text(value,speed))
                        speed = get_speed()
                    elif key.upper() == "CTRL":
                        section.add(Control(value))
                    elif key.upper() == "CMD":
                        section.add(Command(value,speed))
                        speed = get_speed()
                    else:
                        print(f"Unexpected key {key} in section {section_name}")
            self.sections.append(section)


def run():
    if len(sys.argv) != 2:
        print("Usage: itt script.yaml")
    else:
        story_script = StoryScript(sys.argv[1])
        story_script.run()


if __name__ == '__main__':
    run()
pmqtt
User
Beiträge: 4
Registriert: Freitag 4. Dezember 2020, 09:00

Wow, wow, wow...
Noch nie erlebt, dass sich jemand soviel Mühe für ein Feedback gegeben hat.

Danke dir!
Was hältst du von der Idee, wenn ich fragen darf ?
Antworten