Tag Archives: python

I tried to recreate an optical illusion

My friend Tyrone posted something he recorded from TV.
It was an illusion, using rotated images.

The effect is that it seems that the card is rotating at different speeds, when pressing the s (show/unshow) key, you see the card rotating at the same speed as before.

So I wanted to try to recreate this using python.
The effect is there, but a little less.
What can I improve?

Mine:

Around the 30 seconds mark I disable the background, you’ll see the card rotating as before.

Original:

Better version, larger and using s key to toggle water off, to see the card rotating

import pygame
import math

# 20240409 added s to toggle 

pygame.init()
screen = pygame.display.set_mode((1600, 900))
clock = pygame.time.Clock()

def blitRotate(surf, image, pos, originPos, angle):

    image_rect = image.get_rect(topleft = (pos[0] - originPos[0], pos[1]-originPos[1]))
    offset_center_to_pivot = pygame.math.Vector2(pos) - image_rect.center
    rotated_offset = offset_center_to_pivot.rotate(-angle)
    rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)
    rotated_image = pygame.transform.rotate(image, angle)
    rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)
    surf.blit(rotated_image, rotated_image_rect)

try:
    image = pygame.image.load('cards.png').convert_alpha()
    image2 = pygame.image.load('clear+sea+water-2048x2048.png').convert_alpha()
except:
    text = pygame.font.SysFont('Times New Roman', 50).render('imagemissing', False, (255, 255, 0))
    image = pygame.Surface((text.get_width()+1, text.get_height()+1))
    image2 = image
    image.blit(text, (1, 1))

w, h = image.get_size()
angle = 0
angle2 = 0
done = False
while not done:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

    pos = (screen.get_width()/2, screen.get_height()/2)
    
    screen.fill(0)
    keys = pygame.key.get_pressed()
    if (not keys[pygame.K_s]):
        blitRotate(screen, image2, pos, (900, 900), angle2)
    blitRotate(screen, image, pos, (w/2, h/2), angle)
    angle += 1
    angle2 += math.sin(math.radians(angle))
    pygame.display.flip()
    
pygame.quit()
exit()

What to do when waiting for your ribs on the smoker. (Programming some python)

This time I used a rub with the following ingredients:
Seasalt, garlic, brown sugar, mustard seeds, paprica, cilantroseeds, black pepper, red pepper, oregano, thyme and cumin.

Doing a simple 3-2-1 smoke session, so .. what to do in dose 6 hours?

Lets make something using a Sense hat and Python.
Same HAT I used for my xmas ornament thingy in our tree.

  • Generate a large maze (80×80 for now)
  • Paint the maze using colors on the SenseHat
  • Read joystick movement and scroll the maze accordingly, keeping the player in the middle

Now I have to paint my ribs with BBQ sauce, and leave it in the smoker for yet another hour. (Nice glazing)

Next steps for the maze:

Use a better way to generate (reverse backtracking as I made for my other maze thing)

Wall collision detection is nearly completed.

Better placement “birth” of player in the maze.

# # # # # # # # # # # # # # # # # # # # 

# R D . . . . . R D . . . . R R D R D # 

# D L . . . . . U D . . . . U . R U D # 

# D . . . . R R U R D . . R U U L . D # 

# R D . . . U . . . R R D U . . U L D # 

# . R D R R U . . . . . D U . . . U L # 

# . . D U L . . . . . D L U . . . . . # 

# . . R R U . . . . . R R U . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# . . . . . . . . . . . . . . . . . . # 

# # # # # # # # # # # # # # # # # # # # 

Big media button V2

Back in 2019 I made a volume/mute button using an ATtiny85.
(Digispark/trinkey thingy)

Same device as my password paster

It’s USB connection is perfect for this password paste thingy, but not for a big button like this. (even with a ugly usb extending cable)

2019 Version using digispark ATtiny85

Button is 3D printed (found on yeggi)

For my big battlestation i’m using:

The old way of flashing using Arduino IDE (for digispark)

Install Boards using : preferences, add board URL
http://digistump.com/package_digistump_index.json

Note: There being no regular USB device, you need to add some udev rules.
cat /etc/udev/rules.d/digispark.rules
SUBSYSTEM==”usb”, ATTR{idVendor}==”16d0″, ATTR{idProduct}==”0753″, MODE=”0660″, GROUP=”dialout”

When compiling and uploading the program, you get a message to plug in the device. See below screenshot.

Now the 2024 change.
Reason to change:

  • Want to have USB-C
  • Python to get a more flexible setup
  • I want to use more pins, so I can add LEDs and more buttons.
  • I wanted to play with my Waveshare RP2040 Zero.

This is the first setup, with same functionality as before.

Now I can add more stuff!

Putting the code on the RP2040-zero

Press boot button and insert into your pc.
Download uf2 file from here and save in RP2 drive.
https://circuitpython.org/board/waveshare_rp2040_zero/
Open Thonny, and configure interpreter to:

Download the zip file from https://github.com/adafruit/Adafruit_CircuitPython_HID
And copy only the subdirectory adafruit_hid to the drive in subdir lib

Open the file code.py from the device, and remove example hello world code.
Paste in the following code.

import rotaryio
import board
import time

import board
import digitalio
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode

but = digitalio.DigitalInOut(board.GP4)
but.direction = digitalio.Direction.INPUT
but.pull = digitalio.Pull.UP

cc = ConsumerControl( usb_hid.devices )

encoder = rotaryio.IncrementalEncoder(board.GP5, board.GP6)
last_position = 0
while True:
    position = encoder.position
    if int(last_position) < int(position):
        #print(position)
        command = ConsumerControlCode.VOLUME_DECREMENT
        cc.send(command)
    #last_position = position
    if int(last_position) > int(position):
        #print(position)
        command = ConsumerControlCode.VOLUME_INCREMENT
        cc.send(command)
    last_position = position
    if not but.value:
        command = ConsumerControlCode.MUTE
        cc.send(command)
        time.sleep(0.5)

Above code is the bare minimum, I’ll add more functionality soon.
(LEDs and more buttons)
Next and Previous Track and mode change.
From Audio to Navigation for example.

Micropython Pico W Internal Led Test

  • Download firmware from here (uf2 file)
    https://micropython.org/download/RPI_PICO_W/
    (Make sure you use W version if you have a pico-w)
  • Press bootsel and plug your pico into an usb port.
  • A folder should be accessible
  • cp RPI_PICO_W-20231005-v1.21.0.uf2 /media/$USER/RPI-RP2/
    (pico reboots and installs firmware)

Install and start Thonny.

Tools > Options > Interpreter

Python test code (1 second blink)
Note: for the normal pico without Wi-Fi, it is GP25 instead of LED.

from machine import Pin
import utime

led_onboard = Pin('LED', Pin.OUT)
while True:
        led_onboard.on()
        utime.sleep(1)
        led_onboard.off()
        utime.sleep(1)

Run at boottime?

File > save as:
Select device and name the python script main.py

80×86 boot demo generic work plus code optimalisation and tricks

Writing tools and effects for my new boot demo.

  • Started a generic sector read/writer
  • Some effects
  • A sin/cos data writer to include into your source
  • Working on a library of functions (sector loaders, color palette, vert/hor retrace functions)
  • Laying out a memory map for the demo

Below the output of the sin/cos generator ( see used in video below )
(It also shows a visual plot of the function)

Source code python script

# importing the required module
import matplotlib.pyplot as plt
import numpy as np
import math

# Change these
numberofdatapoints = 360
maxamp = 180
howmuchfromwave = 0.5
numberofharmonies = 1
# Number of harmonies are sin/cos additions in the calculation line below

# not here
step = 360/numberofdatapoints*howmuchfromwave
offset = maxamp
maxamp = maxamp / numberofharmonies
offset = 0


x = [ ]

for xv in range(numberofdatapoints):
    xvstep=xv * step
# Calculation line
#    datapoint=np.sin(math.radians(xvstep))
# Double harmony example
    datapoint=np.sin(math.radians(xvstep)) + (np.sin(math.radians(xvstep*3))/2)

    datapoint=datapoint * maxamp
    datapoint=datapoint + offset
    x.append(int(datapoint))

print("    db ", end="")
print(*x, sep = ",") 
  
# plotting the points 
plt.plot(x)
  
# naming the x axis
plt.xlabel('x - axis')
# naming the y axis
plt.ylabel('y - axis')
  
# giving a title to my graph
plt.title('Example')
  
# function to show the plot
plt.show()

Minimalistic very fast boot loader flash screen effect

Graffiti bouncher test (probably ends up bounching a 320×400 image)
This one uses the generated sintab (Using the python script above)

Test code for a text scroller

Code optimalisation/tricks

clear a (double) register?
xor ax,ax
is faster than
mov ax,0h

Want to make ds pointer same as cs?
Instead of
mov ax,cs
mov ds,ax
use
push cs
pop ds

self modifying code
mostly we just move data around, but you also can change the runtime code (instructions)

  • a – increment ax on line 103h
  • b – another part of the code/maybe in a interrupt
    10Fh load al with 48h (thats the opcode for decrement (see c)
    111h place the opcode in address 103h, which had 40h ..
    Now we changed the code to decrement next time

Speedcode

Populair on the C64 where resources are even more limited, you could use speedcode.
Most of the speedcode you generate, due to its repeating lines.
When looking at clock cycles you can save some extra cycles, by using a little more memory but saving on “expensive” loops.

Simple example

Left a funtion with a loop, right is the same but all instuctions sequencial

Left 15 bytes but 284 cycles

Right 39 bytes but only 102 cycles!

4
4
; below part 9 times
9
3
4
16 or 4
= 284 cycles

Speedcode
4
2 ; xor is faster
9
3 ; even 2 when you can use BX pair!
9
3
9
3
9
3
9
3
9
3
9
3
9
3
9
= 111 cycles (or 102 BX pair)

Moving memory blocks (No DMA)

;DS:(E)SI to address ES:(E)DI
    push cs          ; example to set es to code segment
    pop es
    mov si,1000      ; offset pointer source
    xor di,di        ; destination offset
    mov cx,320*100   ; number of transfers (See below words)
    mov ax,0a000h    ; Destination
    mov es,ax        ; destination segment 
    cld              ; Clear direction flag set SI and DI to auto-increment
    rep movsw        ; repeat mov words! until number of transfers is 0
;  

Short binary > bcd > dec (ascii) convert for numbers (0-99)

mov ax,01ch ; = 28 
mov bx,0ah ; = 10
div bl ; divide BL by AX
       ; AX = 0802 ; Remainder/Divider
xchg ah,al ; change around (dont use if you want to keep little endian)
add ax,3030h ; offset to ascii 30=0 31=1
             ; ax ends up with 3238 .. 28 

Busy day: PHP, Python, RP2040 and Frequency detection.

While watching a online python course, I was writing the code for a music guessing game (Highland Bagpipe Tunes)
The core is working, now it’s a matter of filling this “pruts” with tunes.

Switching between python, php, bash and C is a nightmare 🙂

A screenshot of work in progress

Then the postman came .. with goodies.
I needed the MAX9814 analog microphone with amplifier, all of my other sensors were not up to the task.

So I switched to this WIP with the MAX9814.
I want to make a little gadget using an Arduino and 9 leds, which uses FFT to blink which note I am playing on my Highland Pipes.

So detecting is working, now I have to attach a bunch of leds.

First test using Arduino Cloud (I still prefer PlatformIO) But this is better than the old IDE. (Note, you have to install an agent to connect your browser to a board)

Next thing I did today:
Getting my waveshare RP-2040 Zero working with micropython.

Great the little NeoPixel Led on the board.

Steps to get this working:

  • Install Thonny
  • Connect the rp2040 via USB with the boot button pressed
  • place RPI_pico.xxxx.uf2 on the mounted usb disk, it will reboot
  • Run Thonny connect and run a test program

Want to run program @boot ?
save -> to device, and call main.py

Triple screen panorama viewer

Got a question, could I make the video viewer also for images.

Well, that is a great idea, i’ve got some panoramic photos myself.

A little modification, some added code, but here is a working example.

Some vacation pictures widescreen …

CODE
imageview.py filename
Use esc to stop, and enter for next image.
(Has better full screen experience than my movie player. (no padding) have to revisit that one )

Nice to have?

  • Back button?
  • Comments, renaming thumb
from pathlib import Path
from sys import platform as PLATFORM
import os
import re
import PySimpleGUI as sg
from PIL import Image, ImageEnhance, ImageTk, ImageOps, ImageFilter
from xml.etree import ElementTree as ET
import sys
from sys import platform as PLATFORM

abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)

try:
    image=sys.argv[1]
except:
    print(sys.argv[0] +  " filename")
    exit()


def nextFile(currentfile,dir):
        newfile=""
        dirpath = os.path.dirname(dir)
        fileList = []
        for f in os.listdir(dirpath):
            #fpath = os.path.join(dirpath, f)
            fpath = f
            if os.path.isfile(fpath) and f.endswith(('.jpg','.JPG')):
                fileList.append(fpath)
        fileList.sort()
        for i in range(len(fileList)):
            try:
                if (fileList[i]) == currentfile:
                    newfile=fileList[i+1]
                    break
            except:
                newfile=fileList[0]
        return newfile
# yeah i know .. no thumb but full image, change it yourself!
def loadthumb(thumbfile):
    # IF exists
    path_to_file = thumbfile
    path = Path(path_to_file)

    if path.is_file():
        im = Image.open(thumbfile)
        im=ImageOps.contain(im, (5760,5760)) 
        thumbimage = ImageTk.PhotoImage(image=im)

        window['image'].update(data=thumbimage)
    else:
        window['image'].update("")

sg.theme('SystemDefaultForReal')
#------- Layout image only --------#
layout = [
        [[sg.Image('', size=(5760, 1080), key='image',background_color='black',pad=(0, 0))],
          ]]

#------- Set window --------#
window = sg.Window('Triple image player', layout, no_titlebar=True, margins=(0,0),location=(0,0), size=(5760,1080), keep_on_top=True, finalize=True,resizable=False)

window.bring_to_front()
window.Maximize()
window.bind("<Escape>", "-ESCAPE-")
window.bind("<Return>", "-ENTER-")

window['image'].expand(True, True)               

loadthumb(image)
nextfile = image
#------------ The Event Loop ------------#
while True:
    event, values = window.read(timeout=1000)       # run with a timeout so that current location can be updated
    if event == sg.WIN_CLOSED:
        break

    if event == '-ENTER-':
        nextfile = nextFile(nextfile,'./')
        loadthumb(nextfile)

    if event == '-ESCAPE-':
        window.close()
window.close()

Converting images for right resolution from a temp directory filled with large panorama photos

ls temp  | while read; do
	convert -resize 5760x -gravity center  -crop 5760x1080 -auto-orient  "temp/$REPLY" "$REPLY" 
done

Triple screen movie player in python

I didn’t find an easy and working movie player for wide screen setups.
Like double/triple monitor setups.

I’ve got 3x 1920×1080 monitors connected to my battlestation.
With a resolution of 5760×1080

Simple Python code to play a movie fullscreen

# importing vlc module
import vlc
 
# creating vlc media player object
media_player = vlc.MediaPlayer()
 
# media object
media = vlc.Media("movie.mp4")
 
# setting media to the media player
media_player.set_media(media)
media_player.toggle_fullscreen()
 
# start playing video
media_player.play()
 

But trying to get this stable working, I resorted to pysimplegui

#!/usr/bin/env python3

import PySimpleGUI as sg
import vlc
import sys
from sys import platform as PLATFORM

try:
    movie=sys.argv[1] 
except:
    print(sys.argv[0] +  " filename")
    exit()

sg.theme('DarkBlue')

layout = [[sg.Image('', size=(5760, 1080), key='-VID_OUT-')]]
window = sg.Window('Triple movie player', layout, no_titlebar=True, margins=(0,0),location=(0,0), size=(5760,1080), keep_on_top=True, finalize=True,resizable=False)

window.bring_to_front()
window.Maximize()
window.bind("<Escape>", "-ESCAPE-")
window.bind("<Return>", "-ENTER-")

window['-VID_OUT-'].expand(True, True)

inst = vlc.Instance()
list_player = inst.media_list_player_new()

media_list = inst.media_list_new([])
list_player.set_media_list(media_list)
player = list_player.get_media_player()

if PLATFORM.startswith('linux'):
    player.set_xwindow(window['-VID_OUT-'].Widget.winfo_id())
else:
    player.set_hwnd(window['-VID_OUT-'].Widget.winfo_id())

media_list.add_media(movie)
list_player.set_media_list(media_list)
list_player.play()
while True:
    event, values = window.read(timeout=1000)

    if event == sg.WIN_CLOSED:
        break
    if event == '-ENTER-':
        list_player.play()
    if event == '-ESCAPE-':
        list_player.stop()
        window.close()
window.close()

I’ve converted some of my Vuze media to the correct resolution using kdenlive.

I’ve added a new profile. 5760×1080 dont forget to change the display ratio!

Kdenlive howto

NFO creator with poster art generator

Created this for usage with Kodi, for our own personal movies, which can’t be scraped by movie scrapers obviously.
(dvd-rip,digital video, mobile movies, OBS, Vuze and Nikon movies for example)

UPDATE V2 added functions

This is a followup on:

Works on Windows also!

Needed libraries: (install with pip)
pathlib, Pillow, pymediainfo,PySimpleGUI,python-vlc

NFO created

Title – editable (generated from filename)
Duration – mediainfo data in seconds
Plot – Single line type it yourself
Actors – Predefined
Tags – Predefined checkboxes
Country – Pulldown ( I use this as location, Scotland, Asia, Garden)
Year – default 2023, copy button from mobile phone metadata year
Genre – Pulldown

Works

  • File select and loaded
  • Play movie, scrub thretro movie
  • Create snapshot
  • Play/Mute/Pause
  • Playtimer
  • Auto update NFO text field with selectable options
  • Poster art – rotate
  • Poster art – brightness
  • Reload Thumb after snapshotting
  • Year extracted from filename
  • add tag field
  • Next file (button AND functionality)
  • checkboxes from text list
  • Read NFO from file into fields
  • Poster art – aspect ratio

Needs work

Wish list

  • Poster art – contrast?
  • Low – more mediainfo?
  • Low – Media rotate??

The GUI in action (V1)

Kodi example

CODE ( WIP ) V2
I place this python script in every library directory. So i can change the checkboxes and the NFO being generated.
I fill the directory with symlinks for every movie I want to have included in this directory, but this is not needed. (thats the way I like to do things)
(This is the directory I scrape into Kodi)

from pathlib import Path
from sys import platform as PLATFORM
import os
import re
import PySimpleGUI as sg
import vlc
from pymediainfo import MediaInfo
from PIL import Image, ImageEnhance, ImageTk, ImageOps, ImageFilter
from xml.etree import ElementTree as ET


'''
WARNING, NFO overwrites previous!!



'''

alltags = ["music", "pipes", "computer","bbq","fun","art","travel","hobby","family","retro","compilation","retro","vuze"]

#------- WINDOWS FIX --------#
if PLATFORM.startswith('linux'):
    print("yeah linux baby!")
else:
    os.add_dll_directory(r'E:\VLC')

duration=0
tmptag=""

def nextFile(currentfile,dir):
        newfile=""
        dirpath = os.path.dirname(dir)
        fileList = []
        for f in os.listdir(dirpath):
            #fpath = os.path.join(dirpath, f)
            fpath = f
            if os.path.isfile(fpath) and f.endswith(('.mp4', '.mov', '.mpg','.avi','.mkv','.3gp')):
                fileList.append(fpath)
        fileList.sort()
        for i in range(len(fileList)):
#            print("fileList[i] " + fileList[i])
            try:
                if (fileList[i]) == currentfile:
                    newfile=fileList[i+1]
                    break
            except:
                newfile=fileList
        return newfile

def loadthumb(thumbfile):
    # IF exists
    path_to_file = thumbfile
    path = Path(path_to_file)

    if path.is_file():
        im = Image.open(thumbfile)
        im=ImageOps.contain(im, (640,640)) 
        thumbimage = ImageTk.PhotoImage(image=im)
        window['thumb'].update(data=thumbimage)
    else:
        window['thumb'].update("")

def loadnfo(file):

    #LOAD NFO
    nfo=file + ".nfo"
    path = Path(nfo)

    if path.is_file():
            f = open(nfo, "r")
            innfo=f.read()
            f.close()
#            print ("file : " + nfo)
            innfo="".join([s for s in innfo.strip().splitlines(True) if s.strip()])

# Clear some fields
            window['year'].update("")
            window['plot'].update("")

            # Update fields test
            if ET.fromstring(innfo).find('country') is not None:
                nfolocation = ET.fromstring(innfo).find('country')
                window['location'].update(value=nfolocation.text)
            if ET.fromstring(innfo).find('year') is not None:
                nfoyear = ET.fromstring(innfo).find('year')
                window['year'].update(nfoyear.text)
            if ET.fromstring(innfo).find('plot') is not None:
                nfoplot = ET.fromstring(innfo).find('plot')
                window['plot'].update(nfoplot.text)
            if ET.fromstring(innfo).find('title') is not None:
                nfotitle = ET.fromstring(innfo).find('title')
                window['-NM-'].update(nfotitle.text)
            if ET.fromstring(innfo).find('genre') is not None:
                nfogenre = ET.fromstring(innfo).find('genre')
                window['genre'].update(value=nfogenre.text)

            window['coline'].update(False)
            window['henri'].update(False)
            window['monique'].update(False)
            for actor in ET.fromstring(innfo).findall('actor'):
                name = actor.find('role').text
                if name == "Coline":
                    window['coline'].update(True)
                if name == "Henri":
                    window['henri'].update(True)
                if name == "Monique":
                    window['monique'].update(True)
            for alltag in alltags:
                window[alltag].update(False)

            if ET.fromstring(innfo).find('tag') is not None:
                for tag in ET.fromstring(innfo).findall('tag'):
                    if tag.text in window.AllKeysDict:
                        window[tag.text].update(True)

            window['coline'].update(False)
            window['henri'].update(False)
            window['monique'].update(False)
            for actor in ET.fromstring(innfo).findall('actor'):
                name = actor.find('role').text
                if name == "Coline":
                    window['coline'].update(True)
                if name == "Henri":
                    window['henri'].update(True)
                if name == "Monique":
                    window['monique'].update(True)
            for alltag in alltags:
                window[alltag].update(False)

            if ET.fromstring(innfo).find('tag') is not None:
                for tag in ET.fromstring(innfo).find('tag'):
                    window[tag.text].update(True)

    else:
        innfo=""
    window['LOADNFO'].update(innfo)





#------- Button definition --------#
def btn(name):  
    return sg.Button(name, size=(6, 2), pad=(1, 1))

#def tag(name):  
#    return sg.Checkbox(name, enable_events=True)

#------- GUI definition & setup --------#
sg.theme('SystemDefaultForReal')


l1=sg.Text("New title")
l2=sg.Multiline(" ", expand_x=True, key='-OUT-', expand_y=True,justification='left', size=(20,15))
l3=sg.Multiline(" ", expand_x=True, key='LOADNFO', expand_y=True,justification='left', size=(20,15))
l4=sg.Text("Plot")
t1=sg.Input("", key='-NM-')
t2=sg.Input("", key='plot',enable_events=True)
cb=[]

for alltag in alltags:
    cb.append(sg.Checkbox(alltag, key=alltag, enable_events=True))




cb.append(sg.Input("", key='addtag',size=(5, 1), enable_events=True))
cb.append(sg.Combo(["Lab","Datacenter","home","Outside","Outside","Lloydwebber","Steenweg","Enschede","Netherlands","Germany","Italy","Scotland","Canada","Egypt","Belgium","Sweden","Ireland","Asia","NewZealand","Hilversum"],default_value='Lab',key='location'))
cb.append(sg.Combo(["Storage","Funny","Vacation","Music","Relation"],default_value='Storage',key='genre'))
nameyear=[]
nameyear.append(sg.Checkbox("Coline", key='coline', enable_events=True,default=True))
nameyear.append(sg.Checkbox("Henri", key='henri', enable_events=True,default=True))
nameyear.append(sg.Checkbox("Monique", key='monique', enable_events=True,default=False))
nameyear.append(sg.Input("2023",key='year',size=(5, 1), enable_events=True))
nameyear.append(sg.Button("copyyear\nmetadata", key='copymeta', size=(20, 1), pad=(1, 1), enable_events=True))
nameyear.append(sg.Input("",key='metayear',size=(5, 1), enable_events=True))
nameyear.append(sg.Button("copyyear\nfilename", key='copyname', size=(20, 1), pad=(1, 1), enable_events=True))
nameyear.append(sg.Input("",key='fileyear',size=(5, 1), enable_events=True))
b1=sg.Button("WRITE NFO", key='write', enable_events=True)
b2=sg.Button("Exit", key='exit', enable_events=True)

#------- Layout total --------#

layout = [[sg.Input(key='-VIDEO_LOCATION-', visible=False, enable_events=True),
          sg.FileBrowse(file_types=(("Video files", "*.mkv *.mov *.mp4 *3gp *avi *mpg"),)),btn('next')],
          [sg.Text('Load media to start', key='-MESSAGE_AREA-')],
          [sg.Image('', size=(600, 480), key='-VID_OUT-', pad=(1, 1)),sg.Image('', size=(600, 480), key='thumb', pad=(1, 1))],
          [btn('mute'), btn('play'), btn('pause'), btn('stop'), btn('snap'), btn('snaprot'), btn('lighten'), btn('darken'), sg.Combo(["4:3","16:9","9:16"],key='aspect',enable_events=True)],
          [sg.Slider(range=(0, 1000), default_value=1, expand_x=True, enable_events=True, orientation='horizontal', key='-SL-')],
          [sg.Text('org title', key='orgtitle')],
          [l1, t1, l4, t2],[cb],[nameyear],[b1, l2, l3, b2],
          [sg.Text('GENERATED                            -= mini nfo writer =-                              PREVIOUS', key='footer')]
          ]

#------- Set window --------#
window = sg.Window('Mini NFO generator', layout, element_justification='center', finalize=True, resizable=True)
#------- VID out window --------#
window['-VID_OUT-'].expand(True, True)               
#------------ Media Player Setup ---------#
inst = vlc.Instance()
list_player = inst.media_list_player_new()
media_list = inst.media_list_new([])
list_player.set_media_list(media_list)
player = list_player.get_media_player()
if PLATFORM.startswith('linux'):
    player.set_xwindow(window['-VID_OUT-'].Widget.winfo_id())
else:
    player.set_hwnd(window['-VID_OUT-'].Widget.winfo_id())

#------------ I Want default mute ---------#
player.audio_set_mute(True)

#------------ The Event Loop ------------#
while True:
    event, values = window.read(timeout=1000)       # run with a timeout so that current location can be updated
    if event == sg.WIN_CLOSED:
        break



    if event == 'mute':
        mutevalue = player.audio_get_mute()
        if mutevalue==0:
            player.audio_set_mute(True)
        if mutevalue==1:
            player.audio_set_mute(False)
    if event == 'play':
        list_player.play()
    if event == 'pause':
        list_player.pause()
    if event == 'stop':
        list_player.stop()
    if event == 'snap':
        list_player.pause()
        filename = values['-VIDEO_LOCATION-']
        shortfilename = filename.rsplit( ".", 1 )[ 0 ] 
        newname = shortfilename + "-poster.png"
        player.video_take_snapshot(0, newname, 0, 0)
        loadthumb(newname)
    if event == 'snaprot':
        list_player.pause()
        filename = values['-VIDEO_LOCATION-']
        shortfilename = filename.rsplit( ".", 1 )[ 0 ] 
        newname = shortfilename + "-poster.png"
        im1 = Image.open(newname)
        im1 = im1.rotate(90, Image.NEAREST, expand = 1)
        im1 = im1.save(newname)
        loadthumb(newname)

    if event == 'lighten':
        list_player.pause()
        filename = values['-VIDEO_LOCATION-']
        shortfilename = filename.rsplit( ".", 1 )[ 0 ] 
        newname = shortfilename + "-poster.png"
        im1 = Image.open(newname)
        im1 = ImageEnhance.Brightness(im1)
        im1 = im1.enhance(1.2)
        im1 = im1.save(newname)
        loadthumb(newname)
        
    if event == 'darken':
        list_player.pause()
        filename = values['-VIDEO_LOCATION-']
        shortfilename = filename.rsplit( ".", 1 )[ 0 ] 
        newname = shortfilename + "-poster.png"
        im1 = Image.open(newname)
        im1 = ImageEnhance.Brightness(im1)
        im1 = im1.enhance(0.8)
        im1 = im1.save(newname)
        loadthumb(newname)

    if event == 'aspect':
        filename = values['-VIDEO_LOCATION-']
        shortfilename = filename.rsplit( ".", 1 )[ 0 ] 
        newname = shortfilename + "-poster.png"
        if values['aspect'] == "4:3":
            im1 = Image.open(newname)
            im1 = im1.resize((1920 ,int(1920/4*3)), Image.ANTIALIAS)
            im1 = im1.save(newname)
            loadthumb(newname)
        if values['aspect'] == "16:9":
            im1 = Image.open(newname)
            im1 = im1.resize((1920 ,int(1920/16*9)), Image.ANTIALIAS)
            im1 = im1.save(newname)
            loadthumb(newname)
        if values['aspect'] == "9:16":
            im1 = Image.open(newname)
            im1 = im1.resize((int(1920/16*9),1080), Image.ANTIALIAS)
            im1 = im1.save(newname)
            loadthumb(newname)


    if event == 'write':
        filename = values['-VIDEO_LOCATION-']
        shortfilename = filename.rsplit( ".", 1 )[ 0 ] 
        newname = shortfilename + ".nfo"
        f = open(newname, "w")
        f.write(out)
        f.close()
        loadnfo(shortfilename)

    if event == 'copymeta':
        if datefromfile is not None:
            window['year'].update(datefromfile)

    if event == 'copyname':
        if fileyear is not None:
            window['year'].update(fileyear)


    if event == '-SL-':
        pos=int(values['-SL-'])
        player.set_position(pos/ 1000.0)

        
    if values['coline'] == False:
         colinetext = ""
    if values['coline'] == True:
        colinetext = """<actor>
  <name>Coline Lastname</name>
  <role>Coline</role>
</actor>"""
    if values['henri'] == False:
         henritext = ""
    if values['henri'] == True:
        henritext = """<actor>
  <name>Henri Aanstoot</name>
  <role>Henri</role>
</actor>"""
    if values['monique'] == False:
            moniquetext = ""
    if values['monique'] == True:
        moniquetext = """<actor>
  <name>Monique Lastname</name>
  <role>Monique</role>
</actor>"""

        
    if event == '-VIDEO_LOCATION-':
        if values['-VIDEO_LOCATION-'] and not 'Video URL' in values['-VIDEO_LOCATION-']:
            media_list.remove_index(0)
            media_list.add_media(values['-VIDEO_LOCATION-'])
            list_player.set_media_list(media_list)
            shortname = values['-VIDEO_LOCATION-']
            shortname = shortname.rsplit( "/", 1 )[ 1 ] 
            nextfile=nextFile(shortname,"./")
            shortname = shortname.rsplit( ".", 1 )[ 0 ] 
            window['-NM-'].update(shortname) # only add a legit submit
            list_player.stop()
            list_player.next()
            thumbname=shortname + "-poster.png"
            loadthumb(thumbname)

            #Print org title
            window['orgtitle'].update(values['-VIDEO_LOCATION-'])


            loadnfo(shortname)


            #DURATION
            mi = MediaInfo.parse(values['-VIDEO_LOCATION-'])
            duration=int(mi.tracks[0].duration/1000)
            
            #DATE FROM media
            metadate=mi.tracks[0].encoded_date
            if metadate is not None:
                datefromfile=next(iter(re.findall(r"[1][9][8-9][0-9]|[2][0][0-9]{2}", metadate)), None)
                window['metayear'].update(datefromfile)

            # YEAR FROM FILENAME 
            fileyear=next(iter(re.findall(r"[1][9][8-9][0-9]|[2][0][0-9]{2}", shortname)), None)
            # year from filename
            if fileyear is not None:
                window['fileyear'].update(fileyear)


    if event == 'next':
        
        values['-VIDEO_LOCATION-'] = nextfile
        window['-VIDEO_LOCATION-'].update(nextfile)

        if values['-VIDEO_LOCATION-'] and not 'Video URL' in values['-VIDEO_LOCATION-']:
            media_list.remove_index(0)
            media_list.add_media(values['-VIDEO_LOCATION-'])
            list_player.set_media_list(media_list)
            shortname = values['-VIDEO_LOCATION-']
 #           shortname = shortname.rsplit( "/", 1 )[ 1 ] 
            nextfile=nextFile(shortname,"./")
            #print(nextfile)
            shortname = shortname.rsplit( ".", 1 )[ 0 ] 
            window['-NM-'].update(shortname) # only add a legit submit
            list_player.stop()
            list_player.next()
            thumbname=shortname + "-poster.png"
            loadthumb(thumbname)

            #Print org title
            window['orgtitle'].update(values['-VIDEO_LOCATION-'])
            

            loadnfo(shortname)

            #DURATION
            mi = MediaInfo.parse(values['-VIDEO_LOCATION-'])
            duration=int(mi.tracks[0].duration/1000)
            
            #DATE FROM media
            metadate=mi.tracks[0].encoded_date
            if metadate is not None:
                datefromfile=next(iter(re.findall(r"[1][9][8-9][0-9]|[2][0][0-9]{2}", metadate)), None)
                window['metayear'].update(datefromfile)

            # YEAR FROM FILENAME 
            fileyear=next(iter(re.findall(r"[1][9][8-9][0-9]|[2][0][0-9]{2}", shortname)), None)
            # year from filename
            if fileyear is not None:
                window['fileyear'].update(fileyear)
#        media_list.add_media(nextfile)
##        shortname = nextfile.rsplit( ".", 1 )[ 0 ] 
##        window['-NM-'].update(shortname) # only add a legit submit
#        print(nextfile)
#        window['-VIDEO_LOCATION-'].update(nextfile)
#        list_player.next()
# hier moeten wat update dingen bij!




    # Add tag
    if event == 'addtag':
        tmptag="<tag>" + values['addtag'] + "</tag>"

#------------ exit ---------#

    if event=='exit':
         exit()
#------------ TAGS ---------#
    
    subs=[x.Text for x in cb if x.get()==True]
    for idx, x in enumerate(subs):
        subs[idx]="<tag>" + subs[idx] + "</tag>"


    if values['year'] == False:
        year=str(2023)
    else:
        year=values['year']
    if values['location'] == False:
        location="Lab"
    else:
        location=values['location']
    if values['plot'] == False:
        plot=""
    else:
        plot=values['plot']
    if values['genre'] == False:
        genre="Storage"
    else:
        genre=values['genre']
#------------ NFO TEXT FIELD ---------#

    out="""<movie>
<title>{}</title>
<plot>{}</plot>
<genre>{}</genre>
<duration>{}</duration>
<year>{}</year>
<country>{}</country>
{}
{}
{}
{}
{}
</movie>
""".format(values['-NM-'],plot,genre,duration,year,location,colinetext,henritext,moniquetext,tmptag, "\n".join(subs))
    out="".join([s for s in out.strip().splitlines(True) if s.strip()])
    window['-OUT-'].update(out)

#------------ Messages and timer ---------#

    # update elapsed time if there is a video loaded and the player is playing
    if player.is_playing():
        window['-MESSAGE_AREA-'].update("{:02d}:{:02d} / {:02d}:{:02d}".format(*divmod(player.get_time()//1000, 60),
                                                                     *divmod(player.get_length()//1000, 60)))
    else:
        window['-MESSAGE_AREA-'].update('Load media to start' if media_list.count() == 0 else 'Ready to play media' )

window.close()

Python pysimplegui with VLC example

A work in progress – see followup

pip3 install python-vlc && pip3 install PySimpleGUI

Kodi movie NFO file editor test

And a player in python

Some example code

from pathlib import Path

import PySimpleGUI as sg
import vlc
from sys import platform as PLATFORM


sg.theme('DarkBlue')

def btn(name):  
    return sg.Button(name, size=(6, 1), pad=(1, 1))

b1=sg.Button("WRITE NFO")
b2=sg.Button("Exit")

layout = [[sg.Input(key='-VIDEO_LOCATION-', visible=False, enable_events=True),
          sg.FileBrowse(file_types=(("MP4 Files", "*.mp4"),))],
          [sg.Image('', size=(600, 480), key='-VID_OUT-')],
          [btn('mute'), btn('play'), btn('skip 5sec'), btn('pause'), btn('stop'), btn('snap')],
          [sg.Text('Load media to start', key='-MESSAGE_AREA-')],
          [sg.Slider(range=(0, 100), default_value=5, expand_x=True, enable_events=True, orientation='horizontal', key='-SL-')],
          [[b1, b2]]]

window = sg.Window('Mini NFO generator', layout, element_justification='center', finalize=True, resizable=True)

window['-VID_OUT-'].expand(True, True)                

inst = vlc.Instance()
list_player = inst.media_list_player_new()
media_list = inst.media_list_new([])
list_player.set_media_list(media_list)
player = list_player.get_media_player()
if PLATFORM.startswith('linux'):
    player.set_xwindow(window['-VID_OUT-'].Widget.winfo_id())
else:
    player.set_hwnd(window['-VID_OUT-'].Widget.winfo_id())

player.audio_set_mute(True)
while True:
    event, values = window.read(timeout=1000)       
    if event == sg.WIN_CLOSED:
        break

    if event == 'play':
        list_player.play()
    if event == 'pause':
        list_player.pause()
    if event == 'stop':
        list_player.stop()
    if event == 'snap':
        list_player.pause()
        newname = values['-VIDEO_LOCATION-'] + ".png"
        player.video_take_snapshot(0, newname, 400, 300)
    if event == 'next':
        player.set_position(0.5)
        list_player.play()
    if event == 'mute':
        player.audio_set_mute(True)
    if event == '-SL-':
        player.set_position(int(values['-SL-']/100))
    if event == '-VIDEO_LOCATION-':
        if values['-VIDEO_LOCATION-'] and not 'Video URL' in values['-VIDEO_LOCATION-']:
            media_list.add_media(values['-VIDEO_LOCATION-'])
            list_player.set_media_list(media_list)
            window['-VIDEO_LOCATION-'].update('Video URL or Local Path:') 
            list_player.next()

    if player.is_playing():
        window['-MESSAGE_AREA-'].update("{:02d}:{:02d} / {:02d}:{:02d}".format(*divmod(player.get_time()//1000, 60),
                                                                     *divmod(player.get_length()//1000, 60)))
    else:
        window['-MESSAGE_AREA-'].update('Load media to start' if media_list.count() == 0 else 'Ready to play media' )

window.close()