At the finish line

At the finish line

The project is almost complete! I used chatGPT to help me create a simple GUI with TKinter, and then used PyInstaller to bundle everything up into an executable (.exe) file. This self-contained executable can be stored on a flash drive and run on any computer without installing. I had read that portable apps can be slower-performing due to the speed of usb information transfer, however i don’t notice a difference after trying it.

The flash drive I bought for the purpose

The UX is just how I wanted. The user can just double click the .exe file to launch the GUI, select a video file and click “transcribe”, the .srt file automatically opens in notepad, the user proofreads and translates, then saves and closes, and clicks “add to video”. The subtitled mp4 video appears in the output folder.

I had some trouble with Windows Defender flagging the executable as malware. The name of the supposed malware is Trojan:Win32/Wacatac.B!ml. I did a little bit of reading, and it seems that this is commonly a false positive when developing apps in python. Still, I want to be able to distribute this file to my coworkers and for them to not worry.

I digitally signed the app by creating a self-signed certificate. I’ll include it with the .exe file when sharing, so users can double click the .cer to open a wizard and add it to their trusted store (thereby telling their system to trust the app and not give malware warnings). I also created a text document explaining and proving instructions about this.

I also submitted the file to Microsoft Security Intelligence for Malware Analysis. This will give me more assurance that the file isn’t malware. I’m still waiting for the results, which can take a few days.

Can now transcribe using faster whisper

Can now transcribe using faster whisper

I completed the tutorial showing how to use faster-whisper and ffmpeg to transcribe audio. I now have a Python script that will transcribe audio, generate a subtitle file, and add subtitles to the video with the click of a button.

currently, I run this tool using the CLI and a virtual Python environment. I am interested in creating a very simple GUI using tkinter. The GUI would allow the user to use the script without using the CLI.

I want them to be able to generate the subtitle file and add the subtitles to the video as two separate steps. This is so that they can proofread and edit the transcribed text before the subtitles are actually added to the video. The user would first click a “select file” button. Then, they would click “transcribe” to run the first part of the script (audio extraction, transcription and generate the subtitle file). When the .srt file is created, it would automatically be opened in a notepad (just the notepad app already on their computer). The user would proofread and edit this, then save it. Then the user would click an “add subtitles to video” button to run the add_subtitle_to_video part of the script.

I want this to be configured for bundling as a standalone .exe with all dependencies included, in order to be used as a portable app on a flash drive.

Troubleshooting all morning, but made progress

Troubleshooting all morning, but made progress

Following the digital ocean tutorial, I had a hang-up when I tried to install the faster-whisper package and couldn’t. It turned out that CTranslate2, which faster-whisper relies on, is not compatible with the Python version I was using. I then downloaded and installed Python version 11 and tried again and was successful. All that took 3 hours of troubleshooting. Well, now I know.

I have now created the project directory, downloaded the input video that will be used in the tutorial, set up a virtual environment, activated it, and installed the necessary Python packages.

Side Quest

Side Quest

The last few days, I have been going on a side-quest into a project related to my work at the English center. While it is not geospatial-related, I am attempting to build an app to solve a problem at work.

I have been using chatGPT to guide me through setting up what I need to install what I need. I installed a standalone Python, FFMPEG, Git (to download Whisper), and Visual Studio code (in order to set it as the default text editor for Git). I also downloaded the Python extension for Git.

I have also been using the command line, which is a first for me. I’ve learned how to edit system environment variables to add a new path so that the command line can find Python. I was able to download Whisper via Git using the command line.

Now that I have all the tools installed, I’m considering whether to try to use the script transcribe.py that ChatGPT wrote for me, or to follow the DigitalOcean tutorial. But for now I shall call it a day. Tomorrow.

Process of learning from a textbook

Process of learning from a textbook

In this post, I will write some about how I’ve adapted to learning from a textbook. Though I did learn using a textbook in community college, that was as part of an instructor-led course, so there was more guidance. Since I started learning on my own, I have been using websites which integrate many more screenshots and other supporting features. Now that I have begun learning from a traditional textbook, it requires more initiative and resourcefulness on my part. This was a change, but I feel I have adapted to it.

One of the most important things I’ve learned to do is to retype all code I find in the book. I keep the notepad open in a small window at the top of my screen, and any code I see in the book, I retype in the notepad. This helps to examine closely and understand the code in detail.

Today, I progressed 5 more pages in the textbook. Below is a screenshot of a temporary memory layer I added (there’s a line between two points, but it’s hard to see).

Beginning to make progress with learning PyQGIS

Beginning to make progress with learning PyQGIS

Since writing my last post, I have both gone on to do another tutorial with qgistutorials.org as well as give the book PyQGIS Programmer’s Guide another try.

In this tutorial from qgistutorials.org, I used a custom Python expression and the map tips feature in QGIS to display the name and UTM zone of a city when you hover the mouse over it (shown in the feature image of this post).

The tutorial itself was easy, since it wasn’t about learning to write the code itself, just how to add the custom expression in QGIS. Still, I retyped the given code into a notepad as a learning exercise.

I’ve also made another attempt to learn from The PyQGIS Programmer’s Guide by Gary Sherman, and this time has had more momentum. The first attempt I was daunted by everything it instructed to do to set up the development environment, such as having Qt5 and having qgis installed in a directory without any spaces in the path, etc. It was a lot to take in. But I took it bit by bit and googled a lot of things, and got past it. I realized that OSGeo4W contains everything I need, and I re-downloaded it to a directory which contains no spaces.

I’ve been going through the book page by page, with QGIS open and looking at both as I go. I go really slow and I google a lot. I’m on page 99 now, and I’ve learned a lot. A lot of what I’ve learned has been separate from the content, just googling stuff I didn’t understand and reading about this and that. (For example, “what is ogr” and “what is a provider” and “why does my computer’s file paths have backslashes instead of forward slashes”.)

The screenshot below was when I was using PyQGIS to add and style layers on the canvas. That was exciting, because it finally felt like I was doing PyQGIS.

Cracking open the box on PyQGIS

Cracking open the box on PyQGIS

Since I completed the Learn Python 3 course on CodeCademy last week, I have been seeking an introduction to PyQGIS through a few avenues.

I first opened up a textbook called Extending QGIS with Python by Gary Sherman. I was daunted by having to download Python and some other things (Qt5?) that I had never heard of. The way that the book explains how to install these things, I just felt like it assumes I know my way around a computer more than I do. Maybe this book is for a more advanced level than I am. I decided to try out other resources.

I then chatted with chatGPT about learning PyQGIS. I fiddled around with copying different scripts that ChatGPT gave me into the QGIS python console and seeing the outputs. That was valuable, at least in how it showed me how the Python console in QGIS can be used. Additionally, I tried just retyping chatGPT’s scripts into a notepad as an exercise to familiarize myself with the functions and syntax of PyQGIS. I found that I could read and understand a lot more after the Python course I took.

I still felt that learning from a book or tutorial created by a human was a better way, so I thought to try going back to qgistutorials.org for the PyQGIS tutorials there. I’ve already completed many other tutorials produced by Ujaval Gandhi, so they are easy for me to follow. I did the first two (of seven) tutorials. The below map of mean annual rainfall in Seattle was the product of the second tutorial, in which I used the zonal statistics tool on 12 raster layers at once using the Python console.

By the way, the pattern of more rainfall over the mountains is caused by something called orographic lifting, “where moist air is forced to rise over the mountain slopes, causing it to cool and condense into precipitation, leading to heavier rainfall on the windward side of the mountain range”.

Python course: Classes

Python course: Classes

In this guided project, I used classes and object-oriented programming to create a system for a restaurant. This project was the last in a series of modules in the Learn Python 3 course by CodeCademy.

Since classes and object-oriented programming proved to be challenging concepts for me, I used the walk-through video to follow along with an experienced programmer. Even after completing the project, I don’t feel like I have an adequate understanding of classes. I plan to find other resources online to help me gain that understanding, because I know that classes are an important Python concept particularly for PyQGIS.

class Business:
  def __init__(self, name, franchises):
    self.name = name
    self.franchises = franchises

class Franchise:
  def __init__(self, address, menus):
    self.address = address
    self.menus = menus

  def __repr__(self):
    return self.address

  def available_menus(self, time):
    available_menus = []
    for menu in self.menus:
      if time >= menu.start_time and time <= menu.end_time:
        available_menus.append(menu)
    return available_menus

class Menu:
  def __init__(self, name, items, start_time, end_time):
    self.name = name
    self.items = items
    self.start_time = start_time
    self.end_time = end_time

  def __repr__(self):
    return self.name + ' menu available from ' + str(self.start_time) + ' to ' + str(self.end_time)

  def calculate_bill(self, purchased_items):
    bill = 0
    for purchased_item in purchased_items:
      if purchased_item in self.items:
        bill += self.items[purchased_item]
      else:
        print(f"Item {purchased_item} not found in menu.")
    return bill

#Brunch menu
brunch_items = {
  'pancakes': 7.50, 'waffles': 9.00, 'burger': 11.00, 'home fries': 4.50, 'coffee': 1.50, 'espresso': 3.00, 'tea': 1.00, 'mimosa': 10.50, 'orange juice': 3.50
}
brunch_menu = Menu('Brunch', brunch_items, 1100, 1600)

#Early Bird Menu
early_bird_items = {
  'salumeria plate': 8.00, 'salad and breadsticks (serves 2, no refills)': 14.00, 'pizza with quattro formaggi': 9.00, 'duck ragu': 17.50, 'mushroom ravioli (vegan)': 13.50, 'coffee': 1.50, 'espresso': 3.00,
}
early_bird_menu = Menu('Early Bird', early_bird_items, 1500, 1800)

#Dinner Menu
dinner_items = {
  'crostini with eggplant caponata': 13.00, 'caesar salad': 16.00, 'pizza with quattro formaggi': 11.00, 'duck ragu': 19.50, 'mushroom ravioli (vegan)': 13.50, 'coffee': 2.00, 'espresso': 3.00,
}
dinner_menu = Menu('Dinner', dinner_items, 1700, 2300)

#Kids Menu
kids_items = {
  'chicken nuggets': 6.50, 'fusilli with wild mushrooms': 12.00, 'apple juice': 3.00
}
kids_menu = Menu('Kids', kids_items, 1100, 2100)

menus = [brunch_menu, early_bird_menu, dinner_menu, kids_menu]

flagship_store = Franchise('1232 West End Road', menus)
new_installment = Franchise('12 East Mulberry Street', menus)

basta = Business("Basta Fazoolin' with my Heart", [flagship_store, new_installment])

#Arepa

arepas_items = {
  'arepa pabellon': 7.00, 'pernil arepa': 8.50, 'guayanes arepa': 8.00, 'jamon arepa': 7.50
}
arepas_menu = Menu("Take a' Arepa", arepas_items, 1000, 2000)

arepas_place = Franchise("189 Fitzgerald Avenue", [arepas_menu])

arepa = Business("Take a' Arepa", [arepas_place])
Python Course: Files

Python Course: Files

In this project, I worked with files using Python. This was part of the learning module on files, which is for the Python course I am taking.

import csv

#Retrieve usernames from a csv file
compromised_users = []
with open('passwords.csv') as password_file:
  password_csv = csv.DictReader(password_file, delimiter=',')
  for row in password_csv:
    password_row = row
    compromised_users.append(password_row['Username'])

#write usernames to a new file
with open('compromised_users.txt', 'w') as compromised_user_file:
  for user in compromised_users:
    compromised_user_file.write(user + "\n")

import json

#create a JSON file to notify the boss
with open('boss_message.json', 'w') as boss_message:
  boss_message_dict = {"recipient": "The Boss", "message": "Mission Success"}
  json.dump(boss_message_dict, boss_message)

#remove the original passwords file and replace with rival hacker's signature.
with open("new_passwords.csv", 'w') as new_passwords_obj:
  slash_null_sig = """ _  _     ___   __  ____             
/ )( \   / __) /  \(_  _)            
) \/ (  ( (_ \(  O ) )(              
\____/   \___/ \__/ (__)             
 _  _   __    ___  __ _  ____  ____  
/ )( \ / _\  / __)(  / )(  __)(    \ 
) __ (/    \( (__  )  (  ) _)  ) D ( 
\_)(_/\_/\_/ \___)(__\_)(____)(____/ 
        ____  __     __   ____  _  _ 
 ___   / ___)(  )   / _\ / ___)/ )( \
(___)  \___ \/ (_/\/    \\___ \) __ (
       (____/\____/\_/\_/(____/\_)(_/
 __ _  _  _  __    __                
(  ( \/ )( \(  )  (  )               
/    /) \/ (/ (_/\/ (_/\             
\_)__)\____/\____/\____/
"""
  new_passwords_obj.write(slash_null_sig)