WordSesh EMEA 2020 Kicks Off September 2, Featuring Short Talks and Micro-Tutorials

The second edition of WordSesh EMEA (Europe, Middle East, and Africa) is happening in less than 48 hours on September 2. The virtual event’s schedule is tailored to attendees living in the Eastern hemisphere. In its first year running, WordSesh EMEA attracted a solid turnout of close to 1,000 attendees and was the proving ground for future regional WordSesh events. With WordCamp Europe cancelling in-person events until 2022, WordSesh EMEA is another way to highlight voices in the European WordPress community with a global audience.

“There were definitely enough people at the inaugural WordSesh EMEA event that it was clear I needed to do it again, and expand into an APAC event,” organizer Brian Richards said.

Richards had WordSesh APAC (Asia/Pacific) on the schedule for the end of March, right when the pandemic was breaking out. WordCamp Asia had just been cancelled the month before, as he was preparing to announce speakers for the WordSesh.

“I worked with the WCAsia organizers to invite as many speakers to speak as I could,” Richards said. “I ended up helping run a Feb 22 pop-up livestream event to host several of them and invited the remainder to join me on a second full day of sessions at WordSesh.”

Six months later, many WordCampers have had their fill of online events after months of mandated lockdowns and voluntary quarantining. Any new virtual event announced is immediately in competition with outdoor activities and hobbies that attendees could be engaging in away from the computer. Nevertheless, Richards has seen a threefold increase in signups over the previous year.

“There are 3,200 folks registered currently, and we’re on track to have 3,500 by the time things kick off on Sept 2nd,” he said.

“I’ve been extremely mindful of virtual event fatigue for this one. This time around, the event is designed to fit inside an 8-hour span, inclusive of breaks, and no single session is longer than 20 minutes (plus Q&A).”

Whereas the previous WordSesh events were somewhat novel in that they gave attendees 24 hours of free live streaming WordPress presentations, pandemic era virtual events are evolving to eat up less of attendees’ free time. Organizers are now opting for scheduling fewer speakers for more manageable sections of time, or requiring them to give more condensed versions of their presentations.

“I worked with the speakers to compress their talks into 20 minutes instead of the historical 40 minutes of years past,” Richards said. “I’ve been developing a theory that case studies and micro-tutorials are the most optimal format for a conference talk, though, and I’m going to test that idea more thoroughly in future events.”

The schedule features 13 sessions on topics ranging from plugin development to client management to marketing with multisite networks. Whether you are a PHP developer learning how to navigate the world of blocks or an event planner seeking resources for the pandemic pivot, there’s a session for everyone.

The talks will be a mix of live and pre-recorded, all broadcast live at their set times with speakers joining for Q&A after each session.

“Most will be pre-recorded to eliminate any technical risks during the event (e.g. internet outage, power outage, and all the many various life emergencies),” Richards said. “This also offers a nice bonus of the speaker being able to interact with the chat during their talk.”

WordSesh EMEA will introduce a temporary job board, reminiscent of the kind one might find on a whiteboard in the hallway at a traditional WordCamp.

“Attendees can share whether they are hiring or looking for work in an entirely free-form format,” Richards said. “The entire board will be accessible to attendees throughout the live event only. After the event has ended I will capture the final state of the board and share that with everyone who actually attended so they can have a semi-permanent record. I think this is the first time there has ever been anything that only exists for live attendees and nobody else.”

Richards will also be delivering virtual swag to attendees’ inboxes again this year, with substantial discounts to various WordPress shops, hosts, and service providers. If you haven’t signed up yet, you are not too late. Registration is still open until the last minute, and tickets are free.

Creating a GUI Wrapper for VLC Media Player in python/wxpython

Introduction

I have a pile of home movie videos created over more than seventy years. As with my many photos (all in digital form) they require some effort to find particular ones of interest. Typically, I name my videos (and photos) with tags. A file might look like

    2013-10-29 11-59 Tucker & Cooper playing in backyard.mp4

What I wanted was an integrated interface that would easily allow me to search and play videos. I had been using Everything for the searching, and VLC Media Player for the playback. I found this to be functional but clumsy so I decided to write my own application. I still wanted VLC as my back end because it plays just about everything. Fortunately all of the functionality is bundled in a core library, and the Python gods have seen to it that there is a Python module to interface with that library.

This tutorial will walk you through the creation of a GUI front end for VLC Media Player. It assumes that you are at least passingly familiar with Python and wxPython. If you are not I suggest that for an introduction to Python you read the excellent book Beginning Python: From Novice to Professional by Magnus Lie Hetland. For wxPython there is Creating GUI Applications with wxPython by Mike Driscoll.

Following the (hopefully) successful completion of the GUI front end I will provide the complete video library application. If you work through the tutorial you should be able to understand the workings of the final project.

If you are unfamiliar with wxPython I suggest you read through my previous tutorial on creating a [Python/wxPython Sudoku Tool]().

Quick plug - this tutorial was created using the free version of [Markdown Pad]()

Requirements

I created this application on a laptop running Windows 10 Home although, in theory, it should run on Linux and Mac systems as well with only minor modifications. Of the following packages, you will not actually need wxGlade but I highly recommend it for developing GUIs in wxPython.

Actually, any 3.x version of Python will do although there are two features of 3.8 that I use. One is the walrus operator. If you are not familiar with it, it is a special assignment operator used only within logical expressions. It allows you to do an assignment and a test in one line (something C programmers will be familiar with). It basically eliminates the necessity or priming a loop. Instead of doing

x = GetValue()
while x != someValue:
    stuff
    more stuff
    x = GetValue()

you can do

while (x := GetValue()) != someValue:
    stuff
    more stuff

It's just cleaner. The name of the operator comes from its similarity to a walrus' nose and tusks.

The other is used mainly for debug statements. In development code you will often see statements like

print("x=", x)

In 3.8 this can be shortened to

print(f'{x='})

You should use pip to install/update packages for Python. To make sure you have the latest version of pip installed, open a command shell and type

python -m pip install --upgrade pip

wxPython is a Python wrapper by Robin Dunn for the popular wxWidgets library. This library allows you to create GUI applications that render as native applications whether they run on Windows, Linux, or Mac systems. To install wxPython, open a command shell and type

pip install wxPython

If you already have wxPython installed, run the following to make sure you are using the latest version:

pip install -U wxPython

Obviously if you are building a VLC front end you will need the VLC package to build on. Make sure to download and install the latest version. You will have to explicitly tell Python where to find the core library. You'll see how to do this later.

[VLC Python Interface Library]()

You'll need to import this Python package to control a VLC instance. Install it by:

pip install python-vlc

wxGlade is a GUI design tool that you can use to quickly build your interface. It provides a preview window so you can see your changes in real time. This is particularly useful when using wxPython sizers as the settings can be confusing. Use wxGlade to create event stubs which you can fill in later. You can also use wxGlade to add code to the events but I prefer to just create the stubs and fill in the code later using Idle or Notepad++ as my code editor. Idle is more convenient and flexible for debugging (you can run your code from within Idle) while Notepad++ supports code folding but does allow you to execute the code directly.

wxGlade is not installed from within Python. Download it as a zip file and unzip it into a local folder. Create a shortcut to wxglade.pyw on your desktop or start menu.

I am not going to detail how to create the GUI using wxGlade here. You can simply copy/paste my code into your own py or pyw file. I've created a short, getting started tutorial on creating a GUI using wxGlade. You can find it [here]().

Some Design Notes

You will often find, at the top of my projects, a line like

DEBUG = True/False

and numerous lines in the code like

if DEBUG: print(...

Typically, a method will look like

def MyMethod(self, parm):
    if DEBUG: print(f'MyMethod {parm=}')

In the case of an event handler

def control_OnEvent(self, event):
    if DEBUG: print(f'MyMethod {event=}')

By setting DEBUG at the top I can easily enable/disable tracing of execution during debugging. The reason I print out event is because event handlers need not be triggered only by events. They can be called by other blocks of code. In the case of a button handler this means that you can simulate a button click by calling the handler like

self.btn_OnClick(None)

And because you can easily test the type of event at run time you can call the handler with any type of parameter. To borrow an example from later on, you can have a volume slider that can

  1. Respond to the user moving the slider (triggered by event)
  2. Reset the volume to a specific value (triggered from code)

This an result in fewer and more concise methods. You'll see how this works later.

Lastly, under debugging, you will see the two lines

if DEBUG: import wx.lib.mixins.inspection
if DEBUG: wx.lib.inspection.InspectionTool().Show()

The first imports a library that provides an inspection tool. The second line displays it. Using this tool you can inspect any element of your application at any time. Take the time to play with it.

The Basic Interface

The GUI that we will create in this tutorial will look like

2020-08-31_220823.jpg

In terms of the organization of the elements, the structure looks like:

Application
    Frame
        Vertical Sizer
            Video Panel
            Video Position Slider
            Horizontal Sizer
                Open button
                Play/Pause Button
                Stop Button
                Spacer
                Mute Button
                Volume Slider

Here is the code for the bare bones GUI:

TITLE = "Python vlc front end"
DEBUG = True

import os
import sys
import wx

if DEBUG: import wx.lib.mixins.inspection

class MyApp(wx.App):

    def OnInit(self):

        self.frame = MyFrame()
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

class MyFrame(wx.Frame):

    def __init__(self):

        super().__init__(None, wx.ID_ANY)

        self.SetSize((500, 500))

        #Create display elements
        self.pnlVideo    = wx.Panel (self, wx.ID_ANY)
        self.sldPosition = wx.Slider(self, wx.ID_ANY, value=0, minValue=0, maxValue=1000)
        self.btnOpen     = wx.Button(self, wx.ID_ANY, "Open")
        self.btnPlay     = wx.Button(self, wx.ID_ANY, "Play")
        self.btnStop     = wx.Button(self, wx.ID_ANY, "Stop")
        self.btnMute     = wx.Button(self, wx.ID_ANY, "Mute")
        self.sldVolume   = wx.Slider(self, wx.ID_ANY, value=50, minValue=0, maxValue=200)

        #Set display element properties and layout
        self.__set_properties()
        self.__do_layout()

        #Create event handlers
        self.Bind(wx.EVT_BUTTON, self.btnOpen_OnClick,   self.btnOpen)
        self.Bind(wx.EVT_BUTTON, self.btnPlay_OnClick,   self.btnPlay)
        self.Bind(wx.EVT_BUTTON, self.btnStop_OnClick,   self.btnStop)
        self.Bind(wx.EVT_BUTTON, self.btnMute_OnClick,   self.btnMute)
        self.Bind(wx.EVT_SLIDER, self.sldVolume_OnSet,   self.sldVolume)
        self.Bind(wx.EVT_SLIDER, self.sldPosition_OnSet, self.sldPosition)

        self.Bind(wx.EVT_CLOSE , self.OnClose)

        if DEBUG: wx.lib.inspection.InspectionTool().Show()

    def __set_properties(self):
        if DEBUG: print("__set_properties")
        self.SetTitle(TITLE)
        self.pnlVideo.SetBackgroundColour(wx.BLACK)

    def __do_layout(self):
        if DEBUG: print("__do_layout")

        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)

        sizer_1.Add(self.pnlVideo, 1, wx.EXPAND, 0)
        sizer_1.Add(self.sldPosition, 0, wx.EXPAND, 0)
        sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
        sizer_2.Add(self.btnOpen, 0, 0, 0)
        sizer_2.Add(self.btnPlay, 0, 0, 0)
        sizer_2.Add(self.btnStop, 0, 0, 0)
        sizer_2.Add(80,23) #spacer
        sizer_2.Add(self.btnMute, 0, 0, 0)
        sizer_2.Add(self.sldVolume, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)

        self.Layout()

    def OnClose(self, event):
        """Clean up, exit"""
        if DEBUG: print(f'OnClose {event=}')
        sys.exit()

    def btnOpen_OnClick(self, event):
        """Prompt for, load, and play a video file"""
        if DEBUG: print(f'btnOpen_OnClick {event=}')

    def btnPlay_OnClick(self, event): 
        """Play/Pause the video if media present"""
        if DEBUG: print(f'btnPlay_OnClick {event=}')

    def btnStop_OnClick(self, event):
        """Stop playback"""
        if DEBUG: print(f'btnStop_OnClick {event=}')

    def btnMute_OnClick(self, event):
        """Mute/Unmute the audio"""
        if DEBUG: print(f'btnMute_OnClick {event=}')

    def sldVolume_OnSet(self, event):
        """Adjust volume"""
        if DEBUG: print(f'sldVolume_OnSet {event=}')

    def sldPosition_OnSet(self, event):
        """Select a new position for playback"""
        if DEBUG: print(f'sldPosition_OnSet {event=}')

if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

Two of the methods, __set_properties and __do_layout were generated by wxGlade when I built the interface. They closely resemble the form.Designer.vb file that vb.Net uses to store the creation and setup of controls used in the associated form.vb file. If you examine the code and run the application as is you should see how the controls are put together to create the interface.

Adding Some Functionality

The first thing we are going to add is some code to the self.btnOpen handler so we can get a video loaded. Because we are going to autoplay a video on load we'll also add the code to initialize the VLC media component. The VLC core functionality is in the file libvlc.dll. This file in in the folder where VLC was installed, which on my computer is C:\Program Files\VideoLAN\VLC. We will have to tell Python where to find it before we import the vlc code. We modify our imports to look like:

import os
os.add_dll_directory(r'C:\Program Files\VideoLAN\VLC')

import sys
import wx
import vlc

import wx.lib.mixins.inspection

We create the VLC interface and player objects in MyFrame.init as follows:

self.instance = vlc.Instance()
self.player = self.instance.media_player_new()

and we tell the player that we want to render the video in self.pnlVideo by passing it a handle to the panel.

self.player.set_hwnd(self.pnlVideo.GetHandle())

Every other VLC operation will be done through the player object.

Now we can add the code to the btnOpen handler. If we are going to load a new video it makes sense to stop the current video from playing (if there is one) so we will also add code to the btnStop event handler.

def btnStop_OnClick(self, event):
    """Stop playback"""
    self.player.stop()

def btnOpen_OnClick(self, event):
    """Prompt for and load a video file"""

    #Stop any currently playing video
    self.btnStop_OnClick(None)

    #Display file dialog
    dlg = wx.FileDialog(self, "Choose a file", r'd:\my\videos', "", "*.*", 0)

    if dlg.ShowModal() == wx.ID_OK:
        dir  = dlg.GetDirectory()
        file = dlg.GetFilename()

        self.media = self.instance.media_new(os.path.join(dir, file))
        self.player.set_media(self.media)

        if (title := self.player.get_title()) == -1:
            title = file
        self.SetTitle(title)

        #Play the video
        self.btnPlay_OnClick(None)

and some code to the btnPlay handler as

def btnPlay_OnClick(self, event): 
    """Play/Pause the video if media present"""
    if DEBUG: print(f'btnPlay_OnClick {event=}')
    if self.player.get_media():
        self.player.play()

You should be able to run this and play a video. If you see any output lines like the following you can just ignore them:

[0000022c9dd12320] mmdevice audio output error: cannot initialize COM (error 0x80010106)
[0000022c9dd20940] mmdevice audio output error: cannot initialize COM (error 0x80010106)

It's nice to be able to pause and resume a video so let's modify the Play button so it acts like a toggle.

def btnPlay_OnClick(self, event): 
    """Play/Pause the video if media present"""

    if self.player.get_media():
        if self.player.get_state() == vlc.State.Playing:
            #Pause the video
            self.player.pause()
            self.btnPlay.Label = "Play"
        else:
            #Start or resume playing
            self.player.play()
            self.btnPlay.Label = "Pause"

def btnStop_OnClick(self, event):
    """Stop playback"""
    self.player.stop()
    self.btnPlay.Label = "Play"

Note that I've added a line in btnStop_OnClick to update the Play button label.

You'll probably have noticed that we have a slider to show the position of the video during playback but the slider isn't moving. In order to link the slider to the video we will create a timer object and update the slider position in the timer handler. Create the timer in the block that creates the other elements by

self.timer = wx.Timer(self)

and an associated event handler by

self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)

The event handler will look like

def OnTimer(self, event):
    """Update the position slider"""

    if self.player.get_state() == vlc.State.Playing:
        length = self.player.get_length()
        self.sldPosition.SetRange(0, length)
        time = self.player.get_time()
        self.sldPosition.SetValue(time)

If we set the slider length to the length of the video then on every timer tick we just have to copy the current position as returned by get_time to the slider position. Now all we have to do is start the timer when we start playback. Our play/pause and stop handlers will now look like

def btnPlay_OnClick(self, event): 
    """Play/Pause the video if media present"""

    if self.player.get_media():
        if self.player.get_state() == vlc.State.Playing:
            #Pause the video
            self.player.pause()
            self.btnPlay.Label = "Play"
        else:
            #Start or resume playing
            self.player.play()
            self.timer.Start()
            self.btnPlay.Label = "Pause"

def btnStop_OnClick(self, event):
    """Stop playback"""
    self.timer.Stop()
    self.player.stop()
    self.btnPlay.Label = "Play"

I want to mention here that you may notice volume problems where the volume initially is out of sync with the slider. This appears to be a timing problem with the core library where setting the volume at the start may fail. In order to avoid this we will force the volume to agree with the slider in our timer handler.

Our application so far looks like

TITLE = "Python vlc front end"
DEBUG = True

import os
os.add_dll_directory(r'C:\Program Files\VideoLAN\VLC')

import sys
import wx
import vlc

if DEBUG: import wx.lib.mixins.inspection

ROOT  = os.path.expanduser("~")

class MyApp(wx.App):

    def OnInit(self):

        self.frame = MyFrame()
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

class MyFrame(wx.Frame):

    def __init__(self):

        super().__init__(None, wx.ID_ANY)

        self.SetSize((500, 500))

        #Create display elements
        self.pnlVideo    = wx.Panel (self, wx.ID_ANY)
        self.sldPosition = wx.Slider(self, wx.ID_ANY, value=0, minValue=0, maxValue=1000)
        self.btnOpen     = wx.Button(self, wx.ID_ANY, "Open")
        self.btnPlay     = wx.Button(self, wx.ID_ANY, "Play")
        self.btnStop     = wx.Button(self, wx.ID_ANY, "Stop")
        self.btnMute     = wx.Button(self, wx.ID_ANY, "Mute")
        self.sldVolume   = wx.Slider(self, wx.ID_ANY, value=50, minValue=0, maxValue=200)
        self.timer       = wx.Timer (self)

        #Set display element properties and layout
        self.__set_properties()
        self.__do_layout()

        #Create event handlers
        self.Bind(wx.EVT_BUTTON, self.btnOpen_OnClick,   self.btnOpen)
        self.Bind(wx.EVT_BUTTON, self.btnPlay_OnClick,   self.btnPlay)
        self.Bind(wx.EVT_BUTTON, self.btnStop_OnClick,   self.btnStop)
        self.Bind(wx.EVT_BUTTON, self.btnMute_OnClick,   self.btnMute)
        self.Bind(wx.EVT_SLIDER, self.sldVolume_OnSet,   self.sldVolume)
        self.Bind(wx.EVT_SLIDER, self.sldPosition_OnSet, self.sldPosition)
        self.Bind(wx.EVT_TIMER , self.OnTimer,           self.timer) 

        self.Bind(wx.EVT_CLOSE , self.OnClose)

        #Create vlc objects and link the player to the display panel
        self.instance = vlc.Instance()
        self.player = self.instance.media_player_new()
        self.player.set_hwnd(self.pnlVideo.GetHandle())

        if DEBUG: wx.lib.inspection.InspectionTool().Show()

    def __set_properties(self):
        if DEBUG: print("__set_properties")
        self.SetTitle(TITLE)
        self.pnlVideo.SetBackgroundColour(wx.BLACK)

    def __do_layout(self):
        if DEBUG: print("__do_layout")

        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)

        sizer_1.Add(self.pnlVideo, 1, wx.EXPAND, 0)
        sizer_1.Add(self.sldPosition, 0, wx.EXPAND, 0)
        sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
        sizer_2.Add(self.btnOpen, 0, 0, 0)
        sizer_2.Add(self.btnPlay, 0, 0, 0)
        sizer_2.Add(self.btnStop, 0, 0, 0)
        sizer_2.Add(80,23) #spacer
        sizer_2.Add(self.btnMute, 0, 0, 0)
        sizer_2.Add(self.sldVolume, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)

        self.Layout()

    def OnClose(self, event):
        """Clean up, exit"""
        if DEBUG: print(f'OnClose {event=}')
        sys.exit()

    def btnOpen_OnClick(self, event):
        """Prompt for, load, and play a video file"""
        if DEBUG: print(f'btnOpen_OnClick {event=}')

        #Stop any currently playing video
        self.btnStop_OnClick(None)

        #Display file dialog
        dlg = wx.FileDialog(self, "Select a file", ROOT, "", "*.*", 0)

        if dlg.ShowModal() == wx.ID_OK:
            dir  = dlg.GetDirectory()
            file = dlg.GetFilename()

            self.media = self.instance.media_new(os.path.join(dir, file))
            self.player.set_media(self.media)

            if (title := self.player.get_title()) == -1:
                title = file
            self.SetTitle(title)

            #Play the video
            self.btnPlay_OnClick(None)

    def btnPlay_OnClick(self, event): 
        """Play/Pause the video if media present"""
        if DEBUG: print(f'btnPlay_OnClick {event=}')

        if self.player.get_media():            
            if self.player.get_state() == vlc.State.Playing:
                #Pause the video
                self.player.pause()
                self.btnPlay.Label = "Play"
            else:
                #Start or resume playing
                self.player.play()
                self.timer.Start()
                self.btnPlay.Label = "Pause"

    def btnStop_OnClick(self, event):
        """Stop playback"""
        if DEBUG: print(f'btnStop_OnClick {event=}')
        self.timer.Stop()
        self.player.stop()
        self.btnPlay.Label = "Play"

    def btnMute_OnClick(self, event):
        """Mute/Unmute the audio"""
        if DEBUG: print(f'btnMute_OnClick {event=}')

    def sldVolume_OnSet(self, event):
        """Adjust volume"""
        if DEBUG: print(f'sldVolume_OnSet {event=}')

    def sldPosition_OnSet(self, event):
        """Select a new position for playback"""
        if DEBUG: print(f'sldPosition_OnSet {event=}')

    def OnTimer(self, event):
        """Update the position slider"""

        if self.player.get_state() == vlc.State.Playing:
            length = self.player.get_length()
            self.sldPosition.SetRange(0, length)
            time = self.player.get_time()
            self.sldPosition.SetValue(time)
            #Force volume to slider volume
            self.player.audio_set_volume(self.sldVolume.GetValue())

if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

In just 162 lines of code (104 if you don't count comments, white space, and doc strings) we have a reasonably functional video player. Next we will add functionality for a Mute/Unmute button and a volume slider.

The VLC mute feature is independent of the volume setting so you can mute/unmute the audio without affecting the volume setting. In case you were wondering why I set the max value of the volume slider to 200 when I created the control, it was to match the volume range used by vlc which is 0-200. To implement a mute/unmute feature we simply have to connect a handler to the button and toggle the mute setting and update the button label to indicate the new function. To start, lets make sure that the start volume and slider position are both reasonable and consistent so in the initialization block we will add some code to set a default volume.

#Set the initial volume to 40 (vlc volume is 0-200)
self.player.audio_set_volume(40)
self.sldVolume.SetValue(40)

and the btnMute_OnClick handler will become

def btnMute_OnClick(self, event):
    """Mute/Unmute the audio"""
    self.player.audio_set_mute(not self.player.audio_get_mute())
    self.btnMute.Label = "Unute" if self.player.audio_get_mute() else "Mute"

To change the volume via the slider we can code the handler as

def sldVolume_OnSet(self, event):
    """Adjust audio volume"""
    self.player.audio_set_volume(self.sldVolume.GetValue())

or, as I mentioned earlier we could code it like

def sldVolume_OnSet(self, event):
    """Adjust volume"""
    if DEBUG: print(f'sldVolume_OnSet {event=}')

    if type(event) is int:
        #Use passed integer value as  new volume
        volume = event
        self.sldVolume.SetValue(volume)
    else:
        #Use slider value as new volume
        volume = self.sldVolume.GetValue()

This allows us to trigger the handler from an event or from code. With this form we can replace the earlier initialization of the default volume to

self.sldVolume_OnSet(40)

Before we add a seek function I want to take care of something that I've always found annoying. When I change settings I usually prefer that those settings get restored on the next run of the application. I like to implement the same two methods in most of my applications.

def LoadConfig()
def SaveConfig()

I call the LoadConfig method at the end of my init code, and SaveConfig in my OnClose handler. I don't like little files littering up my system, and I don't want to worry about dragging a config file along when I move an app to another folder so I make use of a feature of Windows NTFS called Alternate Data Streams (ADS for short). In brief when you have a file (or folder) you can create one or more ADS. Ever wonder how when you download a program, Windows knows to ask you if you really want to run it? It's because there is an ADS that tags it as a foreign file. An ADS name is simply the file (or folder) name, followed by a colon, then the stream name. In our case we will have an application file and an ADS with the respective names:

VideoLib.py
VideoLib.py:config

You can create, delete, read, and write an ADS from Python but if you want to see the contents from Windows you can use a command shell and the cat command like

cat < VideoLib.py:config

Incidentally, you can store any kind of data in an ADS. You could even store an executable. But I digress. Command line arguments are available through sys.argv, where item[0] is the name of the executing script. To get the name of the associated config ADS (which we will create) you can do

self.config = __file__ + ':config'

Now you just treat it like any other file. And because Python can happily execute code created on the fly we don't have to worry about parsing standard config file entries. We can just write Python statements and read/execute them.

Having said that, you may choose not to use this method because

  1. Your system does not support ADS (Linux, Mac, Windows non-NTFS)
  2. You just don't like it

That's fine. With just a minor modification you can use a plain old ini file. Set a flag at the top to indicate your preference

USEADS = True #or False

And code LoadConfig as follows:

def LoadConfig(self):
    """Load the settings from the previous run"""
    if DEBUG: print("LoadConfig")

    if USEADS: self.config = __file__ + ':config'
    else:      self.config = os.path.splitext(__file__)[0] + ".ini"

    try:
        with open(self.config,'r') as file:
            for line in file.read().splitlines():
                if DEBUG: print(line)
                exec(line)
    except: pass

Doing a SaveConfig, however, is a little trickier as you must ensure that you are writing out valid Python statements. We want to save the last used position, size, volume and video folder so we will use

def SaveConfig(self):
    """Save the current settings for the next run"""
    if DEBUG: print("SaveConfig")

    x,y = self.GetPosition()
    w,h = self.GetSize()
    vol = self.sldVolume.GetValue()

    with open(self.config,'w') as file:
        file.write('self.SetPosition((%d,%d))\n' % (x, y))
        file.write('self.SetSize((%d,%d))\n'     % (w, h))
        file.write('self.sldVolume_OnSet(%d)\n'  % (vol))
        file.write('self.root = "%s"' % self.root.replace("\\","/"))

We must use self.root.replace("\\","/") to prevent Python from interpreting backslash characters as escape sequences when we do the next LoadConfig. Also, because we are now maintaining a root property we add

self.root = ROOT

to self.__set_properties and change our btnOpen_OnClick code from

dlg = wx.FileDialog(self, "Select a file", ROOT, "", "*.*", 0)

to

dlg = wx.FileDialog(self, "Select a file", self.root, "", "*.*", 0)

we must also update self.root when we browse to a new folder in self.btnOpen_OnClick

self.root = dir

We will call LoadConfig at the end of our initialization and SaveConfig in our OnClose handler.

Note that we never have to change our LoadConfig code. It just merrily tries to execute whatever it reads in. Of course, if you don't like this method you are free to rewrite LoadConfig and SaveConfig or use one of the available Python libraries. Two last notes about the load/save code and ADS:

  1. Editing and saving the base file (VideoLib.py) will delete the ADS
  2. You can edit the ADS directly by notepad VideoLib.py:config

The next feature we will add is the ability to move around within the video stream. To do that, instead of updating the slider with the current playback position, we will update the playback position with the value of the slider. To do that we use the wx.EVT_SLIDER event. We already have a handler stub so let's add the code. Like the sldVolume_OnSet handler we will write it so that it can be called from other code as well as by the event subsystem.

def sldPosition_OnSet(self, event):
    """Select a new position for playback"""
    if DEBUG: print(f'sldPosition_OnSet {event=}')

    if type(event) is int:
        #Use passed value as new position (passed value = 0 to 100)
        newpos = event / 100.0
        self.sldPosition.SetValue(int(self.player.get_length() * newpos))
    else:
        #Use slider value to calculate new position from 0.0 to 1.0
        newpos = self.sldPosition.GetValue()/self.player.get_length()

    self.player.set_position(newpos)

In this case it helps to know that set_position takes a number from 0 to 1.0 where 0, naturally is the start, and 1.0 is the end. To calculate the desired value we divide the new slider position by the video length. Remember that we initially set the maximum slider value to be the video length.

Try playing a video and note that you can now seek to any position in the video.

One thing I always found annoying about VLC is that when you resize a video it doesn't automatically crop the unused space (black borders). We can easily do that with our application though by adding a few lines of code to our timer event. We'll need to get the aspect ratio of the currently playing video and we might as well just bury that in another method. Due to a timing issue which I haven't quite worked out yet, the VLC library methods that return the width and height may return 0 at the start so if that happens we'll just return a default aspect of 4/3. It may cause the video to display in the wrong size frame to start but it will be for such a short period it will likely go unnoticed.

def GetAspect(self):
    """Return the video aspect ratio w/h if available, or 4/3 if not"""
    width,height = self.player.video_get_size()
    return 4.0/3.0 if height == 0 else width/height

Now we can add the following code to the end of our timer handler

#Ensure display is same aspect as video
asp = self.GetAspect()
width,height = self.GetSize()
newheight = 75 + int(width/asp)
if newheight != height:
    self.SetSize((width,newheight))

One more little tweak handles the detection of the event when the video reaches the end. At that point we want to rest the slider to the start and make sure that play/pause button displays the correct label. I originally went through the VLC event manager to handle this but I found it problematic. After spending the better part of two days trying to debug it I gave up and just handled it in my timer code by adding

if self.player.get_state() == vlc.State.Ended:
    self.btnStop_OnClick(None)
    return

and modifying btnStop_OnClick to stop the timer.

def btnStop_OnClick(self, event):
    """Stop playback"""
    if DEBUG: print(f'btnStop_OnClick {event=}')

    self.timer.Stop()
    self.player.stop()
    self.btnPlay.Label = "Play"
    self.sldPosition_OnSet(0)

Now you see why I wrote the sldPosition_OnSet handler as dual-purpose.

If you run this application with the extension py you will see (mostly annoying) informational messages produced by VLC. You can safely ignore these. If they offend you then use the extension pyw and you won't see them again. The complete code for what we have done so far is

TITLE  = "Python vlc front end"
USEADS = False
DEBUG  = True

import os
os.add_dll_directory(r'C:\Program Files\VideoLAN\VLC')

import sys
import wx
import vlc

if DEBUG: import wx.lib.mixins.inspection

class MyApp(wx.App):

    def OnInit(self):

        self.frame = MyFrame()
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

class MyFrame(wx.Frame):

    def __init__(self):

        super().__init__(None, wx.ID_ANY)

        self.SetSize((500, 500))

        #Create display elements
        self.pnlVideo    = wx.Panel (self, wx.ID_ANY)
        self.sldPosition = wx.Slider(self, wx.ID_ANY, value=0, minValue=0, maxValue=1000)
        self.btnOpen     = wx.Button(self, wx.ID_ANY, "Open")
        self.btnPlay     = wx.Button(self, wx.ID_ANY, "Play")
        self.btnStop     = wx.Button(self, wx.ID_ANY, "Stop")
        self.btnMute     = wx.Button(self, wx.ID_ANY, "Mute")
        self.sldVolume   = wx.Slider(self, wx.ID_ANY, value=50, minValue=0, maxValue=200)
        self.timer       = wx.Timer (self)

        #Set display element properties and layout
        self.__set_properties()
        self.__do_layout()

        #Create event handlers
        self.Bind(wx.EVT_BUTTON, self.btnOpen_OnClick,   self.btnOpen)
        self.Bind(wx.EVT_BUTTON, self.btnPlay_OnClick,   self.btnPlay)
        self.Bind(wx.EVT_BUTTON, self.btnStop_OnClick,   self.btnStop)
        self.Bind(wx.EVT_BUTTON, self.btnMute_OnClick,   self.btnMute)
        self.Bind(wx.EVT_SLIDER, self.sldVolume_OnSet,   self.sldVolume)
        self.Bind(wx.EVT_SLIDER, self.sldPosition_OnSet, self.sldPosition)
        self.Bind(wx.EVT_TIMER , self.OnTimer,           self.timer) 

        self.Bind(wx.EVT_CLOSE , self.OnClose)

        #Create vlc objects and link the player to the display panel
        self.instance = vlc.Instance()
        self.player = self.instance.media_player_new()
        self.player.set_hwnd(self.pnlVideo.GetHandle())

        self.LoadConfig()

        if DEBUG: wx.lib.inspection.InspectionTool().Show()

    def __set_properties(self):
        if DEBUG: print("__set_properties")
        self.SetTitle(TITLE)
        self.root = os.path.expanduser("~")
        self.file = ""
        self.pnlVideo.SetBackgroundColour(wx.BLACK)
        self.sldVolume_OnSet(40)

    def __do_layout(self):
        if DEBUG: print("__do_layout")

        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_2 = wx.BoxSizer(wx.HORIZONTAL)

        sizer_1.Add(self.pnlVideo, 1, wx.EXPAND, 0)
        sizer_1.Add(self.sldPosition, 0, wx.EXPAND, 0)
        sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
        sizer_2.Add(self.btnOpen, 0, 0, 0)
        sizer_2.Add(self.btnPlay, 0, 0, 0)
        sizer_2.Add(self.btnStop, 0, 0, 0)
        sizer_2.Add(80,23) #spacer
        sizer_2.Add(self.btnMute, 0, 0, 0)
        sizer_2.Add(self.sldVolume, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)

        self.Layout()

    def LoadConfig(self):
        """Load the settings from the previous run"""
        if DEBUG: print("LoadConfig")

        if USEADS: self.config = __file__ + ':config'
        else:      self.config = os.path.splitext(__file__)[0] + ".ini"

        try:
            with open(self.config,'r') as file:
                for line in file.read().splitlines():
                    if DEBUG: print(line)
                    exec(line)
        except: pass

    def SaveConfig(self):
        """Save the current settings for the next run"""
        if DEBUG: print("SaveConfig")

        x,y = self.GetPosition()
        w,h = self.GetSize()
        vol = self.sldVolume.GetValue()

        with open(self.config,'w') as file:
            file.write('self.SetPosition((%d,%d))\n' % (x, y))
            file.write('self.SetSize((%d,%d))\n'     % (w, h))
            file.write('self.sldVolume_OnSet(%d)\n'  % (vol))
            file.write('self.root = "%s"' % self.root.replace("\\","/"))

    def OnClose(self, event):
        """Clean up, save settings, and exit"""
        if DEBUG: print(f'OnClose {event=}')
        self.SaveConfig()
        sys.exit()

    def btnOpen_OnClick(self, event):
        """Prompt for, load, and play a video file"""
        if DEBUG: print(f'btnOpen_OnClick {event=}')

        #Stop any currently playing video
        self.btnStop_OnClick(None)

        #Display file dialog
        dlg = wx.FileDialog(self, "Select a file", self.root, "", "*.*", 0)

        if dlg.ShowModal() == wx.ID_OK:
            dir  = dlg.GetDirectory()
            file = dlg.GetFilename()

            self.root = dir

            self.media = self.instance.media_new(os.path.join(dir, file))
            self.player.set_media(self.media)

            if (title := self.player.get_title()) == -1:
                title = file
            self.SetTitle(title)

            #Play the video
            self.btnPlay_OnClick(None)

    def btnPlay_OnClick(self, event): 
        """Play/Pause the video if media present"""
        if DEBUG: print(f'btnPlay_OnClick {event=}')

        if self.player.get_media():            
            if self.player.get_state() == vlc.State.Playing:
                #Pause the video
                self.player.pause()
                self.btnPlay.Label = "Play"
            else:
                #Start or resume playing
                self.player.play()
                self.timer.Start()
                self.btnPlay.Label = "Pause"

    def btnStop_OnClick(self, event):
        """Stop playback"""
        if DEBUG: print(f'btnStop_OnClick {event=}')
        self.timer.Stop()
        self.player.stop()
        self.btnPlay.Label = "Play"
        self.sldPosition_OnSet(0)

    def btnMute_OnClick(self, event):
        """Mute/Unmute the audio"""
        if DEBUG: print(f'btnMute_OnClick {event=}')
        self.player.audio_set_mute(not self.player.audio_get_mute())
        self.btnMute.Label = "Unute" if self.player.audio_get_mute() else "Mute"

    def sldVolume_OnSet(self, event):
        """Adjust volume"""
        if DEBUG: print(f'sldVolume_OnSet {event=}')

        if type(event) is int:
            #Use passed value as  new volume
            volume = event
            self.sldVolume.SetValue(volume)
        else:
            #Use slider value as new volume
            volume = self.sldVolume.GetValue()

    def sldPosition_OnSet(self, event):
        """Select a new position for playback"""
        if DEBUG: print(f'sldPosition_OnSet {event=}')

        if type(event) is int:
            #Use passed value as new position (passed value = 0 to 100)
            newpos = event / 100.0
            self.sldPosition.SetValue(int(self.player.get_length() * newpos))
        else:
            #Use slider value to calculate new position from 0.0 to 1.0
            newpos = self.sldPosition.GetValue()/self.player.get_length()

        self.player.set_position(newpos)

    def GetAspect(self):
        """Return the video aspect ratio w/h if available, or 4/3 if not"""
        width,height = self.player.video_get_size()
        return 4.0/3.0 if height == 0 else width/height

    def OnTimer(self, event):
        """Update the position slider"""

        if self.player.get_state() == vlc.State.Ended:
            self.btnStop_OnClick(None)
            return

        if self.player.get_state() == vlc.State.Playing:
            length = self.player.get_length()
            self.sldPosition.SetRange(0, length)
            time = self.player.get_time()
            self.sldPosition.SetValue(time)
            #Force volume to slider volume (bug)
            self.player.audio_set_volume(self.sldVolume.GetValue())

        #Ensure display is same aspect as video
        aspect = self.GetAspect()
        width,height = self.GetSize()
        newheight = 75 + int(width/aspect)
        if newheight != height:
            self.SetSize((width,newheight))

if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

In my next post I am going to take this code and complete the video library project.

Subclassing python objects

Subclassing python objects

Sometimes you want to add just a little more functionality to an existing python object. You can do that by subclassing it and adding on the missing parts. This example is based on a really mindless game of solitaire I used to play as a kid. In this game you start with a shuffled deck. You deal the cards face up one by one. Play doesn't actually start until you have four cards laid out (place them face up, left to right, or fan them out in your hand). Every time you add a card you examine the four most recent cards (rightmost).

If the last card and the fourth last card are the same rank then you remove the last four cards.

If the last card and the fourth last card are the same suit then you remove the two cards between them.

Play continues until you have dealt all the cards. If you have no cards left in your hand at that point then you win.

I knew it was difficult to win a hand (based on never having done so). I wanted to know if it was even possible. Rather than playing and hoping for a win I decided to script it. My first attempt a couple of years back was written in vbScript. This version is written in python. For the record, the vbScript version is 143 lines. The python version is 54 (comments and blank lines not counted).

The basic unit of the game is a card, which consists of a rank (A23..TJQK) and a suit. Both a hand and a deck consist of a list of cards. For the purpose of this game, the only functionality I want to add is the ability to produce a compact string for printing the results.

There are a number of functions that operate the same no matter what objects they are used on. For example, you can get the length of something with the len(something) function, or convert something to a string with the str(something) function. Similarly, you can compare things with logical operators like ==, <=, etc. These are actually implemented on an object by object basis by using the so-called magic methods. I can show you how this works with the card object which (in part) looks like

class Card:

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

By redefining the magic method __eq__ as follows:

def __eq__(self, other):
    return (self.rank == other.rank and self.suit == other.suit)

I can now compare two card objects for equality by

if carda == cardb:

and python will use my overridden __eq__ method to do the comparison. Likewise, I can redefine the magic method __str__ as

def __str__(self):
    return str(self.rank) + self.suit

and when I want to convert card to a string I can just do

str(card)

I have similarly redefined the __str__ method in both the Hand and Deck classes. Purists would say that Hand and Deck are so similar that they should not be separate classes but I thought I would just keep it simple here.

"""                                                                             
    Name:                                                                       

      Solitaire.py                                                             

    Description:                                                                

      This program simulates multiple runs of a pretty mindless game of         
      solitaire that relies only on the luck of the cards. There is no skill    
      involved.                                                                 

      In this game, cards are pulled from the deck one at a time and added to   
      your hand. If you have two cards exactly four apart that have the same    
      suit, remove the two cards between these cards. If you have two cards     
      exactly four cards apart of the same rank remove these two cards AND the  
      two cards between them. The idea is to go through the entire deck and end 
      up with as few cards as possible.                                         

      I wanted to know if it was actually possible to end up with zero cards    
      but I didn't feel like playing for long enough to win so I wrote this     
      instead. It displays the game number, the starting deck, then the hand    
      after every change (card added/cards deleted). It stops when a win is     
      detected (no cards remaining).                                            

    Audit:                                                                      

         2020-08-27  rj  original code                                          

"""                                                                            

import random

class Card:

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit

    def __eq__(self, other):
        return (self.rank == other.rank and self.suit == other.suit)

    def __str__(self):
        return str(self.rank) + self.suit

class Hand(list):

    def __init__(self):
        super().__init__()

    def __str__(self):

        s = ""

        for card in self:
            s += " " + str(card)

        return s.strip()

class Deck(list):

    def __init__(self):
        super().__init__()

        suits = (chr(9824), chr(9829), chr(9830), chr(9827))

        for i in range(52):
            card = Card("A23456789TJQK"[i % 13], suits[i//13])
            self.append(card)

    def __str__(self):

        s = ""

        for card in self:
            s += " " + str(card)

        return s.strip()

games = 0
random.seed()

while True:

    games += 1

    deck = Deck()
    hand = Hand()

    random.shuffle(deck)

    print("\ngame #%d\n" % games)

    #loop until the deck is empty

    while len(deck):

        #draw another card and display the current hand

        hand.append(deck.pop())
        print(str(hand))

        #remove cards as long as we are able

        while True:

            count = len(hand)

            #if rank of last) card = rank of 4th last card then remove last 4

            if len(hand) > 3 and hand[-1].rank == hand[-4].rank:
                for i in range(4): del hand[-1]
                print(str(hand))

            #if suit of last card = suit of 4th last card then remove cards between

            if len(hand) > 3 and hand[-1].suit == hand[-4].suit:
                 for i in range(2): del hand[-2]
                 print(str(hand))

            #quit if no cards were removed
            if count == len(hand): break    

    if len(hand):
        print("\nLOST - %d cards left" % len(hand))
    else:
        print("WON in %d games" % games)

    #if no cards left then we won
    if not len(hand): break

More Labels & Languages – The Hub Just Gets Better

Just when you thought we were done making The Hub better (okay, we’re never done), we spiced it up even further with custom labels and language translations!

Languages and labels give you even more options to set up The Hub to your specifications in just a few clicks.

This cool new feature allows you to translate The Hub into other languages, color-code your websites, and filter your sites by categories & labels so you can sort through them with ease.

Dev Man with paint and language book.
Dev Man brushing-up on his languages and label colors.

Why did we add these features?

Simple. Because based on our survey — you wanted it! And, well, we did, too. We’re pretty fond of new features here.

Here’s a quick break-down on what’s new…

Your Language Preferences Await

It’s essential to read all that’s presented to you in The Hub without translating it. That’s why we’ve now made it simple and easy to change your language preferences.

Pick your language preferences by clicking the gear icon that opens up Account Preferences.

The gear icon in the hub.
The gear icon is waiting for you inside the Hub.

You’ll then have several languages to choose from. Select an option from the dropdown menu and The Hub will be instantly translated.

You can choose from English, French, Portuguese, and Spanish.

It’s as simple as that! Your language preference is one-click away.

P.S. Just around the corner, there will be options for timezones and more languages to choose from. All of this is coming VERY soon.

From Filters to Labels – We’ve Got You Covered!

You can now organize your websites in The Hub with filters and labels! That means you can filter them by category and color labels, which makes it visually more accessible for you to navigate and sort. Plus, name the labels however you’d like.

After all…

“Without a filter, a man is just chaos walking.” – Patrick Ness

When it comes to filtering, let’s take a look at adding a specific color to a site first.

To color-code a site, you just click on the bar to the right of it.

The bar that you click on to change colors.
Uh-oh. This site is gray and dull. Clicking this bar will change that.

Once you do that, you have the options of:

  • None
  • Purple
  • Blue
  • Green
  • Yellow
  • Orange
  • Red
Color options.
Which color is your favorite?

Simply pick out a color, and your website will be represented by it in The Hub.

But wait! There’s more…

You can pick a different color from a color palette, delete colors, edit colors, and filter them. The Filters & Labels dropdown menu is where these options are.

The filters and labels tab.
Want more customization and sorting options? Here’s where it’s at.

When you click on the dropdown, you have the option to Edit.

Where you edit labels.
Not fond of the way purple looks or its name? Change its tone and label it differently.

Here, you can edit any color by clicking on it. Change the name or the color itself.

Want a new color that’s not featured? That’s simple to do. Just hit Add Label.

The add label button.
A variety of colors await.

Bright, dull, neutral — the choice is yours!

Pick from the color palette any color you’d like.

Don’t want to label it a name of a color? No problem. You can label it whatever you would like (e.g. eCommerce).

Example of labeling a label ecommerce.
Name a label anything you want.

When it comes to arranging the labels, drag & drop the colors in any order that suits you.

Drag & drop makes it easy.

For organizing your content, you can sort by Category and Labels.

From the categories, you can filter by:

  • Updates
  • Security
  • Speed
  • SEO
  • Backlogs
  • Uptime

This saves you gobs of time and helps avoid searching burnout.

You’ll see that ‘Updates’ appears on the screen’s lefthand side when selected in the Filter by area.

Example of filters appearing in the hub.
Notice how ‘Updates’ is on the left when selected on the right?

Filtering functions the same with Labels. Pick between any labels/colors that you want to sort through. Or, mix it up with both labels and a category.

Remove the category or labels at any time by clicking on them. Or you can hit Clear All.

The clear all button.
You can take any of these out with one-click or do it all in one swoop with ‘Clear All.’

It’s an awesome way to quickly pull-up what you need and control what content is displayed.

Ready for More? Just You Wait.

In case you haven’t noticed, The Hub is continually growing with newer and better advancements. The labels and languages feature are just a small snippet of what’s to come.

If you’re not a member of WPMU DEV, consider becoming one. You’ll get immediate access to The Hub, award-winning premium plugins, and 24/7 support. Try us free for seven days.

Until our next colorful update, we’ll say goodbye, au revoir, tchau, & adiós!

 

Autograd: The Missing Machine Learning Library

Wait, people use libraries other than TensorFlow and PyTorch?

Ask a group of deep learning practitioners for their programming language of choice and you’ll undoubtedly hear a lot about Python. Ask about their go-to machine learning library, on the other hand, and you’re likely to get a picture of a two library system with a mix of TensorFlow and PyTorch. While there are plenty of people that may be familiar with both, in general commercial applications in machine learning (ML) tend to be dominated by the use of TensorFlow, while research projects in artificial intelligence/ML mostly use PyTorch. Although there’s significant convergence between the two libraries with the introduction of eager execution by default in TensorFlow 2.0 released last year, and the availability of building static executable models using Torchscript, most seem to stick to one or the other for the most part.

Looking for the Best Lightweight Data Analysis Script Tools

Almost all programming languages can manipulate data. Some are too general to lack functions for performing structured computations, such as C++ and JAVA, which produce lengthy code to deal with daily data analysis scenarios and are more suitable for taking care of major special projects. Some are technically-targeted and too highly-professional for daily analysis work, such as mathematical programming languages MATLAB and R, though they provide functions for structured data processing. My subjects in this article are the lightweight programming languages that are suitable for doing desktop analytic jobs. They are lightweight databases represented by MySQL, Excel VBA, Python pandas and esProc.

Now I’ll scrutinize the pros and cons of each to look at their capabilities.

How to Search Google Images by the Exact Size

Google Images earlier offered a useful “search by size” option in advanced search to help you find logos, wallpapers and other images on the Internet by their exact size (or resolution).

For instance, you could limit your search for landscape photographs to image files that were at least 10 Megapixels in size. Or, if you are were using Google Image search to find wallpapers for the desktop, you could specify the image resolution as 1920x1080 pixels and Google would only return large images with those exact dimensions.

Google Image Search by Size

The “exact size” search option is no longer available in Google Image Search but you can still limit your image searches to a particular size by using the secret imagesize search operator in the query itself.

Here’s how.

Go to images.google.com and enter the search terms as before. Then append imagesize:WIDTHxHEIGHT to your query and hit Enter. Google Images will remove the operator from the query but the results will only display images that match the specified size.

The search by size operators works on the mobile version of Google as well so you may use the simple trick to find that perfect size wallpaper for your phone.

More Search Tricks

You an also use search operators in Gmail, Google Drive, YouTube and Twitter to easily find stuff you’re looking for.

Planning Your API Roadmap

Introduction

APIs — the current “big thing” — offer the opportunity for modern organizations to unlock new and lucrative business models. The article below covers some tips on how to spin the API flywheel and leverage its possibilities.

In the API economy, a successful service can gain popularity and be utilized in ways unpredicted and often inconceivable by its original owners. The very flexible nature of the technology opens many doors, including business collaborations, reuse in third-party products, or even conquering hardware barriers by reaching a spectrum of devices.

One Year After Beta, Elmastudio’s Aino Blocks Plugin Lands in the Plugin Directory

One year ago today, Elmastudio launched a beta version of its Aino WordPress theme and Aino Blocks plugin. While the team pushed the theme live in the theme directory earlier this year, it wasn’t until a couple of weeks ago that they officially released their blocks plugin.

Elmastudio’s beta launch was one of the first pieces of news I noted when I began writing for the Tavern — I was already lining up stories a couple of weeks before I officially started. But, the story fell down the list over time. After seeing Aino Blocks land in the plugin directory two weeks ago, I thought it would be interesting to check out what the plugin looked like today.

Aside from a few minor styling issues, the Aino theme is a solid offering for users who are looking for a block-ready theme that provides an open canvas. I am generally a fan of Elmastudio’s work with themes. However, most of the blocks from the Aino Blocks plugin are not particularly impressive. At best, they are par for the course for these types of block library plugins. I tend to install them to see if I can find a gem of an idea or two, some missing element that would solve one of the various problems I have. No such luck this time.

The Plugin’s Blocks

The one block that caught my eye the most was the Grid block. I am still hopeful that core WordPress adopts some sort of grid layout block or system. Therefore, I test every such block I come across.

The Grid block in the Aino Blocks plugin works well enough for people who have a background in CSS code. The terminology for the block options may be hard to understand for average users. On the whole, it does not feel intuitive enough for me to recommend it over better options.

Using the Grid block from the Aino Blocks plugin.
Using the Grid block.

Layout Grid by Automattic still holds the title for best grid block plugin thus far. Yes, its options can be confusing too, but it does provide dragging capabilities that will autofill those settings for less tech-savvy users. GenerateBlocks also has a powerful Grid block that is far easier to use.

The most complex block and the one that seemingly pushes some boundaries is the Hero block. It is essentially a block that combines content, buttons, and media in a specific layout. The problem is that there may not be a need for the block in every case. It is far better suited as a block pattern, and because the plugin already introduces a custom pattern, there is little reason not to move along the same route with the Hero block.

Within a couple of minutes, I was able to recreate the default Hero block output with core WordPress blocks. The only exception to this was the use of the Badge block included with the Aino Blocks plugin.

To recreate the Hero block, an end-user merely needs to add a Media & Text block. In the content/text panel for the block, they can add the Badge, Heading, Paragraph, and Buttons blocks. After adding an image in the media section, they would have recreated the Hero block. Because of the multiple inner blocks involved in this, it can be a bit complicated for some users. That’s where patterns come into play. By using a pattern, the plugin would have:

  • Used less JavaScript.
  • Used less CSS.
  • Mostly used core blocks.

Users may be stuck with an unnecessary block in this case. And, if this is their first foray into the world of blocks, they are unlikely to learn that there was a better way.

The one thing the Hero block brings to the table is its custom grid settings. It provides end-users with control over the placement of content and media columns. That is the only thing it has going for it as an individual block, but such a feature might be better as custom block options, such as those provided through EditorPlus.

Aino Blocks does include a single block pattern. It is called Hero Aino. It is a customized version of the Hero block in pattern form and showcases what users can accomplish with a few adjustments of the block options.

Aino Hero block pattern from the Aino Blocks WordPress plugin.
Aino Hero block pattern.

This pattern is the one area where the plugin shines. It will be interesting to see if the developers continue with more patterns in the future.

The plugin also adds Badge, Card, Author, Testimonial, Buttons, and Arrow Button blocks. The Badge block allows users to add a small bit of text with an inline background. The Arrow Button is essentially a link with an arrow icon next to it. Everything else feels like it has been done before by a plethora of other block collection plugins.

Final Verdict

I question whether most of these types of block library plugins are necessary at this point. Few of them feel like they are pushing any limits, raising the bar beyond what has already been done. My fear is that we will continue to see more and more of these collections packaged from every plugin and theme shop to the point where everyone is simply building the same blocks in-house.

This is why the block directory needs to be integrated into core. Instead of downloading an entire collection of blocks for something like a plain ol’ testimonial block, end-users can simply download a single testimonial block.

Perhaps I am being a bit harsh on Aino Blocks. Maybe it appeared in the plugin directory too late. Bigger plugins have already carved the path that Aino is trekking. I want to see more than yet another block collection by yet another theme/plugin company. I want to be dazzled.

For the most part, the plugin works well. I did not see anything technically wrong with it. I just do not see it appealing to many people outside of Elmastudio’s current theme users, not when there are more mature plugins of its type out there. There is still room to grow. The company’s best bet is to focus on building patterns. Its first pattern shows some promise. I am holding out hope for more interesting work to come.

Guide To Cross Browser Testing On Older Browser Versions

“How do I perform website testing on older browser versions? Is it even necessary?”

Have you ever wondered about these questions? If you did, you’re not the only one. At some point, every web tester or web developer ponders on these. And it is logical to do so. After all, new browser versions are released every month. Which makes it difficult for testers and developers to maintain a record of emerging and deprecated features. Not to forget, the never-ending release requirements are constantly squeezing your bandwidth.

Building an Automated Testing Framework Based on Chaos Mesh and Argo

computer

Chaos Mesh ® is an open-source chaos engineering platform for Kubernetes. Although it provides rich capabilities to simulate abnormal system conditions, it still only solves a fraction of the Chaos Engineering puzzle. Besides fault injection, a full chaos engineering application consists of hypothesizing around defined steady states, running experiments in production, validating the system via test cases, and automating the testing.

This article describes how we use TiPocket, an automated testing framework to build a full Chaos Engineering testing loop for TiDB, our distributed database.

Apache Kafka and SAP ERP Integration Options

A question I get every week from customers across the globe is, "How can I integrate my SAP system with Apache Kafka?" This post explores various alternatives, including connectors, third party tools, custom glue code, and trade-offs between the different options.

After exploring what SAP is, I will discuss several integration options between Apache Kafka and SAP systems:

Looking Under the Hood of Apache Pulsar: How Good is the Code?

Apache Pulsar (incubating) is an enterprise-grade publish-subscribe (aka pub-sub) messaging system that was originally developed at Yahoo. Pulsar was first open-sourced in late 2016, and is now undergoing incubation under the auspices of the Apache Software Foundation. At Yahoo, Pulsar has been in production for over three years, powering major applications like Yahoo! Mail, Yahoo! Finance, Yahoo! Sports, Flickr, the Gemini Ads platform, and Sherpa, Yahoo’s distributed key-value store.

One of the primary goals of the open-source software movement is to allow all users to freely modify software, use it in new ways, integrate it into a larger project or derive something new based on the original. The larger idea being that through open collaboration, we can make a software the best version of itself. So, in the spirit of innovation and improvement, we peeked behind the curtain and ran Apache Pulsar through the free static code analysis tool Embold. The results are very interesting, both from an observer's and a learner's point of view.

Shop signage for business prosperity

Today, there are many ways to promote products and services, and one of these effective methods is signage.

Sign making as one of the advertising tools among the people has developed and become so popular that it is no longer considered a cost but an investment for the business.

Signs are either placed in the form of billboards in squares and crossings and highways or at the entrance of the business premises, or on the roofs and are exposed to the public.

The better the quality, beauty and lighting of the panel, the more effective it will be in attracting customers.

Panel making is designed and implemented in different types such as Chelnium, Flexi, LED, metal, neon plastic, light box and city TV panels. Each of them has its own design and implementation process.

Chelnium embossed panel has gained more fans these days than other types due to its lighting with different and beautiful colors.
Advantages of making a panel:

Signage is more durable than other advertising tools and is resistant to cold and heat and sunshine and dust and does not deteriorate, and if the conditions are provided that you have to move and relocate it can be easily in the area. Moved geographically.

Billboard making is superior to other advertising tools such as leaflets because most people pay very little attention to it when they receive a tract and may even throw it away without looking, but billboards when at business or in the market. And the crossings and highways are installed, they are exposed to the public for a long time, and whether they like it or not, everyone even pays attention to it once, and when they need it, they go to the desired place, and maybe to the customer. Become permanent.

All works except databse wont delete it!!!

<?php 
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

    session_start();
if (isset($_POST['delete_account'])){

    require  'conn.php';
    global $conn;

    $password = $_POST['delete_password'];
    $email = 'delete@gmail.com';
    $user_id = '5';

    $sql = "SELECT * FROM user_acounts WHERE e_pasts=?;";
    $stmt = mysqli_stmt_init($conn); // Inicializcija 

    if(!mysqli_stmt_prepare($stmt, $sql)){
     header ('location: userpanel.php?error');
     exit();   
     }else{
        mysqli_stmt_bind_param($stmt, "s", $email);
        mysqli_stmt_execute($stmt);
        $result = mysqli_stmt_get_result($stmt);

     }
     if ($row = mysqli_fetch_assoc($result)){

        $password_check = password_verify($password, $row["PAROLE"]);      
        if ($password_check == false){  

          header ('location:userpanel.php?password=false');
          exit();

        }else if ($password_check == true){  

            $SQL = "DELETE FROM user_acounts WHERE e_pasts=?;";
            $stmt = mysqli_stmt_init($conn);

            if (!mysqli_stmt_prepare($stmt, $sql))
                    {
                        header('location: userpanel.php?error');
                        exit();
                    }
                    else
                    {

                        mysqli_stmt_bind_param($stmt, "s", $email);
                        mysqli_stmt_execute($stmt);
                        header('location: userpanel.php?delete=true');
                        exit();
                    }
          }
     }

     mysqli_stmt_close($stmt);
     mysqli_close($conn);

}

?>

I am making a thing that user can delete his account so evrebody works except delete form...

Cloudian Announces New Support for Container-Based Apps

Cloudian, an object storage solution provider, has announced support for container-based apps. The new product, Cloudian Kubernetes S3 Operator, is a self-service tool that allows developers to dynamically provision Cloudian HyperStore S3-compatible object storage. The new product extends Cloudian’s existing and growing support for cloud-native environments.