Tag Archives: python

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()

Top IMDB followup using python (openpyxl)

Using some python and a scraped list I can now mark which movies we’ve seen, or have to see.
(Own the movie or have it seen streamed in the cinema)

I just have to place a X after the title in the first column.
Run the python script, and presto

First column, the list we started with.
Second column, mark the movie.
3rd until the end .. years 2000-2023

Blue – seen
Light Blue – seen but was not in original list (so a new movie)
Green – have this movie, but still have to watch it
Light Green – Have this movie but it’s not in the original list
Orange – New in that year (could be an oldie reemerging in the top 250)

Now the python script, maybe it useful for you.

# 20230813 18:49 IMDB overview
# pip install openpyxl

import openpyxl
from openpyxl.styles import PatternFill
from openpyxl.styles.colors import Color
 
wb = openpyxl.load_workbook("clearsheet.xlsx")
sheet = wb['Sheet1']

colors = ['00660066', '00FFFFCC',
          '007b8cf2', '005ace97', '00ffac58','00dddddd','009bd8ff','007aeeb7']
fillers = []

for color in colors:
    temp = PatternFill(patternType='solid',
                       fgColor=color)
    fillers.append(temp)

# Mark found previous years
for colt in range(4, 27):
    for colr in range(3, colt):
        for rowr in range(2, 252):
            for rowrr in range(2, 252):
                if sheet.cell(row=rowrr, column=colr).value == sheet.cell(row=rowr, column=colt).value :
                    sheet.cell(row=rowr, column=colt).fill = fillers[4]

# Mark Our Old list matched with all years
for titlerow in range(2, 252):
    for colr in range(3, 26):
        for rowr in range(2, 252):
            if sheet.cell(row=titlerow, column=1).value == sheet.cell(row=rowr, column=colr).value :
                sheet.cell(row=titlerow, column=1).fill = fillers[5]

# Mark Have / Seen
for title in range(2, 252):
    for j in range(3, 27):
        for i in range(2, 252):
            if sheet.cell(row=i, column=j).value == sheet.cell(row=title, column=1).value :
                if sheet.cell(row=title, column=2).value == "x":
                     sheet.cell(row=i, column=j).fill = fillers[2]
                else:
                     sheet.cell(row=i, column=j).fill = fillers[3]

# Mark non old list but seen
for title in range(252, 400):
    for j in range(3, 27):
        for i in range(2, 252):
            if sheet.cell(row=i, column=j).value == sheet.cell(row=title, column=1).value :
                if sheet.cell(row=title, column=2).value == "x":
                     sheet.cell(row=i, column=j).fill = fillers[6]
                else:
                     sheet.cell(row=i, column=j).fill = fillers[7]
# Info cells
sheet['D255'] = "Downloaded seen org list"
sheet['D256'] = "Downloaded not seen org list"
sheet['D257'] = "Title matches found in 2000-2023"
sheet['D258'] = "Titles found in 2000-2023 not in org list have"
sheet['D259'] = "Titles found in 2000-2023 not in org list have seen"
sheet['D260'] = "Titles found in 2000-2023 previous years"
sheet.cell(row=255, column=4).fill = fillers[2]
sheet.cell(row=256, column=4).fill = fillers[3]
sheet.cell(row=257, column=4).fill = fillers[5]
sheet.cell(row=258, column=4).fill = fillers[7]
sheet.cell(row=259, column=4).fill = fillers[6]
sheet.cell(row=260, column=4).fill = fillers[4]
wb.save("imdbexport.xlsx")

Python Spotify Genre Cube v2

Spotify version

Next to do: Lasercut a wooden cube with better lettering.

Run the python part on a server

and

First export some API credentials

export SPOTIPY_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export SPOTIPY_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export SPOTIPY_REDIRECT_URI="http://localhost:8080/callback"

Code below:

from flask import Flask, request, redirect
from requests_oauthlib import OAuth2Session
from requests.auth import HTTPBasicAuth
import requests
import json
import spotipy
from spotipy.oauth2 import SpotifyOAuth
from pprint import pprint
from time import sleep
import spotipy.util as util
import sys
import paho.mqtt.client as mqttClient
import time
import os
import subprocess

Connected = False


broker_address = "MQTTSERVER"
port = 1883


def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connected to broker")
        global Connected
        Connected =True
    else:
        print("Connection failed")

def on_message(client, userdata, message):
    print (message.payload)
    myurl = 'spotify:playlist:' + str(message.payload.decode())

    results = sp.start_playback(context_uri=myurl, offset={"position": 1})

client = mqttClient.Client("PythonSpotifyGenreCube")
client.on_connect = on_connect
client.on_message = on_message

client.connect(broker_address, port=port,keepalive=60 )
client.loop_start()
time.sleep(4) # Wait for connection setup to complete
client.subscribe('spotify/playlist')
#client.loop_stop()    #Stop loop

#while Connected != True:
#    client.loop()
#    time.sleep(0.1)
#    print("test")
#    client.subscribe('spotify/playlist')



app = Flask(__name__)

AUTH_URL = 'https://accounts.spotify.com/authorize'
TOKEN_URL = 'https://accounts.spotify.com/api/token'
REDIRECT_URI = 'http://localhost:8080/callback'
CLIENT_ID = "xxxxxxxxxxxxxxxxxx"
CLIENT_SECRET = "xxxxxxxxxxxxxxxxxxxxx"
SCOPE = [
    "user-read-email",
    "playlist-read-collaborative"
]

@app.route("/login")
def login():
    spotify = OAuth2Session(CLIENT_ID, scope=SCOPE, redirect_uri=REDIRECT_URI)
    authorization_url, state = spotify.authorization_url(AUTH_URL)
    return redirect(authorization_url)

@app.route("/callback", methods=['GET'])
def callback():
    code = request.args.get('code')
    res = requests.post(TOKEN_URL,
        auth=HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET),
        data={
            'grant_type': 'authorization_code',
            'code': code,
            'redirect_uri': REDIRECT_URI
        })
    return json.dumps(res.json())


username = "fashice"
scope = "user-read-playback-state,user-modify-playback-state,playlist-read-private"
util.prompt_for_user_token(username,scope,client_id='xxxxxxxxxxxxxxxxxxxxx',client_secret='xxxxxxxxxxxxxxxxxxxxxxxxxxxx',redirect_uri='http://localhost:8080/callback')

sp = spotipy.Spotify(client_credentials_manager=SpotifyOAuth(scope=scope))

# Shows playing devices
res = sp.devices()
pprint(res)


# Change track
#sp.start_playback(uris=['spotify:track:6gdLoMygxxxxxxxxxxxxxxx'])
#results = sp.start_playback(context_uri=myurl, offset={"position": 1})


## Change volume
#sp.volume(100)
#sleep(2)
#sp.volume(50)
#sleep(2)
#sp.volume(100)
#


playlists = sp.user_playlists(username)

#for playlist in playlists['items']:
#    print(playlist['name'])


playlists = sp.current_user_playlists()
#print (playlists)
for playlist in playlists['items']:
    print(playlist['id'] + ' ' +  playlist['name'])



#if __name__ == '__main__':
#   app.run(port=8080,debug=True)


while Connected != True:
    time.sleep(0.1)
    client.subscribe('spotify/playlist')

try:
    while True:
        time.sleep(1)


except KeyboardInterrupt:
    print ("exiting")
    client.disconnect()
    client.loop_stop()

Bios hacking

Followup on :

I wrote a little python script which checks the checksum of a Bios.

In a previous post i used hexedit to play around changing a Bios dump.

Below posted python script calculates the checkum using the following:

Add all bytes in the file and do a modulo 256 on this number. The output should be 0.

My previous edit gave me a output of C2, so I changed an unused byte FF into (FF-C2) 3D.
No more checksum errors when booting!

Next to do, get a Bios like Glabios or PcXtBios to start my own code from a secondary Eeprom.

import sys

# Bios sum modulo should be 0
# edit  last or unused byte to fix
# python bios-checksum-test.py MYROM.edit.checksum
# 0

f = open(sys.argv[1],'rb')
m = f.read()
print  '%x' % ( ( sum(ord(c) for c in m) & 0xFFFFFFFF ) % 256 )

Python3

import sys

# Bios sum modulo should be 0
# edit  last or unused byte to fix
# python bios-checksum-test.py MYROM.edit.checksum
# 0

f = open(sys.argv[1],'rb')
m = f.read()
checksum =  ( ( sum((c) for c in m) & 0xFFFFFFFF ) % 256 )
print(checksum, 'in hex =', hex(checksum))

Spotify control using python

For usage in this project:

Goto https://developers.spotify.com and add a APP

Write down the Client_id Client_secret and Redirect URL (callback)

Create a bash and python script

#!/bin/bash
export SPOTIPY_CLIENT_ID=2f660e11e70743febdxxxxxxxxxxxxx
export SPOTIPY_CLIENT_SECRET=b0741452a4fe43xxxxxxxxxxxxx
export SPOTIPY_REDIRECT_URI="http://localhost:8080/callback"

python3 spot.py $1

spot.py

import spotipy
from spotipy.oauth2 import SpotifyOAuth
from pprint import pprint
from time import sleep
import spotipy.util as util
import sys

username = "username"
scope = "user-read-playback-state,user-modify-playback-state,playlist-read-private"
util.prompt_for_user_token(username,scope,client_id=SPOTIPY_CLIENT_ID,client_secret=SPOTIPY_CLIENT_SECRET,redirect_uri=SPOTIPY_REDIRECT_URI)

sp = spotipy.Spotify(client_credentials_manager=SpotifyOAuth(scope=scope))

# Shows playing devices
res = sp.devices()
pprint(res)

myurl = 'spotify:playlist:' + sys.argv[1]

# Change track
results = sp.start_playback(context_uri=myurl, offset={"position": 1})

## Change volume example
#sp.volume(100)
#sleep(2)
#sp.volume(50)
#sleep(2)

playlists = sp.user_playlists(username)

playlists = sp.current_user_playlists()
for playlist in playlists['items']:
    print(playlist['id'] + ' ' +  playlist['name'])

Run bash script as follows.

playlistplayspotify.sh 0bJvpsn0TDxxxxxxxxxxxx
(0bJvpsn0TDxxxxxxxxxxxx – is playlist ID, as example below)

OUTPUT:

python3 spot.py 0bJvpsn0TDxxxxxxxxxxxx
{'devices': [{'id': 'e86eada2a91e29a396acxxxxxxxxxxxxxxxxxxxx',
              'is_active': True,
              'is_private_session': False,
              'is_restricted': False,
              'name': 'laptop',
              'type': 'Computer',
              'volume_percent': 100},
             {'id': '8571468b6c41973ccb0axxxxxxxxxxxxxxxxxxxx',
              'is_active': False,
              'is_private_session': False,
              'is_restricted': False,
              'name': 'DESKTOP-xxxxxxx',
              'type': 'Computer',
              'volume_percent': 76},
             {'id': '6c592503aa5a22b2fbdxxxxxxxxxxxxxxxxxxxxxx',
              'is_active': False,
              'is_private_session': False,
              'is_restricted': False,
              'name': 'TX-NR1030',
              'type': 'AVR',
              'volume_percent': 41}]}
0bJvpsn0TDxxxxxxxxxxxx Best Classical Music Of All Time
5aL9jeGMCAxxxxxxxxxxxx Programming music
37i9dQZEVCxxxxxxxxxxxx Discover Weekly
6pEJuA1UYJxxxxxxxxxxxx Highland/Small/Border Pipe Music
5p8Tabf5Zwxxxxxxxxxxxx Folk Instrumentals ( Celtic, Irish, Nordic, ... )
1oy8Ek4ddBxxxxxxxxxxxx Lounge
37i9dQZF1Dxxxxxxxxxxxx Irish Folk
etc etc

Converting png images to composite pixels (6502)

Using searle’s design, i can draw pixels using composite video out.

Converting b/w png to hex include files, for usage in vasm I did the following.

#Python script to convert black levels to pixels
from PIL import Image
i = Image.open("fash.png")

pixels = i.load() # this is not a list, nor is it list()'able
width, height = i.size

all_pixels = []
for x in range(width):
    for y in range(height):
        cpixel = pixels[x, y]
        if cpixel[1] == 255:
            s = '\t.db 0x05,' + hex(int(x)) + ',' + hex(int(y))
            print (s)

Running and output example

python3 image.py > out

head out
	.db 0x05,0x1,0x16
	.db 0x05,0x1,0x18
	.db 0x05,0x1,0x19
	.db 0x05,0x2,0x7
	.db 0x05,0x2,0x8
	.db 0x05,0x2,0xc
	.db 0x05,0x2,0xd
	.db 0x05,0x2,0x17
	.db 0x05,0x3,0x5

Control codes and vasm include

01 (01) - Cursor home (Standard ASCII)
04 (04) - Cursor solid
05 (05) - Set graphics pixel (next two bytes = x,y) 
0C (12) - Clear screen (Standard ASCII)
0D (13) - Carriage return (Standard ASCII)
0E (14) - Set column 0 to 79 (2nd byte is the column number) or 0 to 39 for a 40 char line
0F (16) - Set row 0 to 24 (2nd byte is the row number)
1B (27) - ESC - reserved for ANSI sequences

vasm include part:

message: 
	.db 0x01,0x0c   ; home and clear
	.db 0x1b,0x2d   ; disable ansi translation
	include "out"   ; include hex "png"
	.db 0x00        ; end with 0 (part of message print routine)



I asked ChatGPT to write code for a Crack the Code game.

The Game

elcome to Crack the Code!
The code has 4 digits. Each digit is between 0 and 9.
You have 10 tries to guess the code.
After each guess, you will be told how many digits you got correct and how many were misplaced.
Good luck!
Enter your guess: 1234
Guess 1: 1234 - 0 correct, 0 misplaced
Enter your guess: 5678
Guess 2: 5678 - 2 correct, 1 misplaced
Enter your guess: 5689
Guess 3: 5689 - 1 correct, 2 misplaced
Enter your guess: 9688 
Guess 4: 9688 - 1 correct, 2 misplaced
....
Sorry, you did not crack the code. The code was 5568.

Generated python code

import random

def generate_code():
    code = ""
    for i in range(4):
        code += str(random.randint(0, 9))
    return code

def check_guess(code, guess):
    correct_digits = 0
    misplaced_digits = 0
    for i in range(4):
        if guess[i] == code[i]:
            correct_digits += 1
        elif guess[i] in code:
            misplaced_digits += 1
    return (correct_digits, misplaced_digits)

def play_game():
    print("Welcome to Crack the Code!")
    print("The code has 4 digits. Each digit is between 0 and 9.")
    print("You have 10 tries to guess the code.")
    print("After each guess, you will be told how many digits you got correct and how many were misplaced.")
    print("Good luck!")
    code = generate_code()
    num_guesses = 0
    while num_guesses < 10:
        guess = input("Enter your guess: ")
        if len(guess) != 4 or not guess.isdigit():
            print("Invalid input. Please enter a 4-digit number.")
            continue
        num_guesses += 1
        correct, misplaced = check_guess(code, guess)
        print(f"Guess {num_guesses}: {guess} - {correct} correct, {misplaced} misplaced")
        if correct == 4:
            print("Congratulations! You cracked the code!")
            return
    print(f"Sorry, you did not crack the code. The code was {code}.")

play_game()

After this i let the AI made some changes, which kinda worked.
Scary nevertheless

Python Baudot code for Wemos Matrix Led

I wrote a python script to generate binary data to include in my Arduino sketch.
This Arduino displays codes send though MQTT.

https://en.wikipedia.org/wiki/Baudot_code

CODE:

python3 matrix.py apple gives me

byte apple_Char[8] = {
  0b00000000,
  0b01000100,
  0b01111000,
  0b00000000,
  0b00110000,
  0b00000000,
  0b00111000,
  0b00000000
};

Python Code
import sys

a = [ 
        [0,0,0,0,0,0,0,0 ], 
        [0,0,0,0,0,0,0,0 ], 
        [0,0,0,0,0,0,0,0 ], 
        [0,0,0,0,0,0,0,0 ], 
        [0,0,0,0,0,0,0,0 ], 
        [0,0,0,0,0,0,0,0 ], 
        [0,0,0,0,0,0,0,0 ], 
        [0,0,0,0,0,0,0,0 ] 
    ] 

letters = [
        [0,1,1,0,0,0,0,0 ],
        [0,1,0,0,0,1,1,0 ],
        [0,0,1,0,1,1,0,0 ],
        [0,1,0,0,0,1,0,0 ],
        [0,1,0,0,0,0,0,0 ],
        [0,1,0,0,1,1,0,0 ],
        [0,0,1,0,0,1,1,0 ],
        [0,0,0,0,1,0,1,0 ],
        [0,0,1,0,1,0,0,0 ],
        [0,1,1,0,0,1,0,0 ],
        [0,1,1,0,1,1,0,0 ],
        [0,0,1,0,0,0,1,0 ],
        [0,0,0,0,1,1,1,0 ],
        [0,0,0,0,1,1,0,0 ],
        [0,0,0,0,0,1,1,0 ],
        [0,0,1,0,1,0,1,0 ],
        [0,1,1,0,1,0,1,0 ],
        [0,0,1,0,0,1,0,0 ],
        [0,1,0,0,1,0,0,0 ],
        [0,0,0,0,0,0,1,0 ],
        [0,1,1,0,1,0,0,0 ],
        [0,0,1,0,1,1,1,0 ],
        [0,1,1,0,0,0,1,0 ],
        [0,1,0,0,1,1,1,0 ],
        [0,1,0,0,1,0,1,0 ],
        [0,1,0,0,0,0,1,0 ]
        ]

number=0
word=str(sys.argv[1])

for col in range(len(word)) :
    character=word[col]

    number = ord(character) - 97
    nextcol = col + 1
    for row in range(len(a[col])) :
        a[row][nextcol] = letters[number][row]


print("byte " + word + "_Char[8] = {")
for i in range(len(a)) : 
    print("  0b", end = '')
    for j in range(len(a[i])) : 
        print(a[i][j], end="")   
    if i < 7:
        print(",")
print()
print("};")

Arduino test code

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiClient.h>


#include "WEMOS_Matrix_LED.h"
MLED mled(5); //set intensity=5

const char* wifi_ssid = "MYSSID"; // Enter your WiFi name
const char* wifi_password =  "MYSSIDPASS"; // Enter WiFi password
const char* mqtt_server = "MYMQTTSERVER";
const int mqtt_port = 1883;
const char* mqttUser = "";
const char* mqttPassword = "";
#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;

WiFiClient espClient;

PubSubClient mqtt(espClient);

void setup_wifi() {
  delay(10);
  WiFi.mode(WIFI_STA);
  WiFi.begin(wifi_ssid, wifi_password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}
 
byte clear_Char[8] = {  
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000,
  0b00000000
};
 
byte baudot_Char[8] = {
  0b11111111,
  0b01101010,
  0b00011100,
  0b11111111,
  0b00110100,
  0b00010000,
  0b00000100,
  0b11111111  
};
 

 
#define TIME 500
 
void setup() { 
    Serial.begin(115200);
      setup_wifi();
        mqtt.setServer(mqtt_server, mqtt_port);

WiFiClient espClient;
PubSubClient mqtt(espClient);

  mqtt.setClient(espClient);
  mqtt.setServer(mqtt_server, mqtt_port);
      delay(500);

  mqtt.subscribe("escape/matrixledin");
        delay(500);

  mqtt.setCallback(callback);

  }

void callback(char* topic, byte* payload, unsigned int length) {
        Serial.println("callback");
    String topicStr = topic;
      byte value = atoi((char*)payload);
        snprintf (msg, MSG_BUFFER_SIZE, "%1d", value);

              mqtt.publish("escape/matrixledout", msg);
       if (value == 1){

drawChar(baudot_Char); 

 }else if (value == 0){
  drawChar(cleat_Char); 
  }else if (value == 2){
  drawChar(test_Char); 
  }else if (value == 3){
  drawChar(no_Char); 
 }
 }

void reconnect() {

  while (!mqtt.connected()) {
    String clientId = "matrixClient-";
    clientId += String(random(0xffff), HEX);
    if (mqtt.connect(clientId.c_str())) {
      mqtt.publish("escape/outTopic", "hello from 8x8led module");
                Serial.println("resubscribe");

      mqtt.subscribe("escape/matrixledin");
        mqtt.setCallback(callback);

    } else {
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
 
void loop() {

 if (!mqtt.connected()) {
          Serial.println("reconnect called");
    reconnect();
  }
    mqtt.loop();

}
 
void drawChar(byte character[8]) {
  for(int y=7;y>=0;y--) {
  for (int x=0; x <= 7; x++) { 
    if (character[(7-y)] & (B10000000 >> x)) {
     mled.dot(x,y); // draw dot
    } else {
     mled.dot(x,y,0);//clear dot
    }
  }
  mled.display();  
  }
}