Dup Ver Goto 📝

Simple Flash Card Overlay App

To
339 lines, 778 words, 8900 chars Page 'Subt_py' does not exist.

Overlay Widget

from icecream import ic; ic.configureOutput(includeContext = True)
import sys
import math
import re
import os
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtNetwork import *
from datetime import datetime

def clamp(x,a,b):
  return max(min(x,b),a)

class Subtitles(QWidget):
  def handleUdp(self):
    try:
      while self.udpSocket.hasPendingDatagrams():
        datagram = self.udpSocket.receiveDatagram(4096)
        data = datagram.data().data()
        self.process_data(data.decode())
    except KeyboardInterrupt:
      print("Ctrl-C")
      QApplication.instance().quit()

  def process_data(self,data):
    data = data[:256].strip()
    cmd,*params = re.split(r"\s+",data,1)
    match cmd:
      case "quit":
        return QApplication.instance().quit()
      case "fontsize":
        try:
          new_font_size = int(params[0])
          new_font_size = min(160,max(32,new_font_size))
          self.fontsize = new_font_size
          self.font = QFont(self.fontname,self.fontsize)
          self.font.setBold(self.bold)
          self.font.setItalic(self.italic)
          self.update()
          return
        except Exception:
          ic("#fail",cmd,params)
      case "bold":
        if len(params) > 0:
          if params[0].lower() in ["true","yes","1"]:
            self.bold = True
          else:
            self.bold = False
        else:
          self.bold = not self.bold
        self.font.setBold(self.bold)
        self.update()
        return
      case "italic":
        if len(params) > 0:
          if params[0].lower() in ["true","yes","1"]:
            self.italic = True
          else:
            self.italic = False
        else:
          self.italic = not self.italic
        self.font.setItalic(self.italic)
        self.update()
        return
      case "fontname":
        try:
          self.fontname = params[0]
          self.font = QFont(self.fontname,self.fontsize)
          self.font.setBold(self.bold)
          self.font.setItalic(self.italic)
          self.update()
          return
        except Exception:
          ic("#fail",cmd,params)
      case "period":
        try:
          new_period = int(params[0])
          new_period = min(300,max(5,new_period))
          new_period *= 1000
          self.subtime = new_period
          self.timer.setInterval(new_period)
          return
        except Exception:
          ic("#fail",cmd,params)
      case "tick":
        self.tick()

  def __init__(self,subtime,src,surround=True,fontsize=120):
    super().__init__()
    self.app = QApplication.instance()

    self.surround = surround
    self.subtime = subtime
    self.lines = src
    self.index = 0 

    port = 4005
    self.udpSocket = QUdpSocket()
    self.udpSocket.readyRead.connect(self.handleUdp)
    self.udpSocket.bind(QHostAddress.Any,port)

    # attributes for display
    self.content = ""
    try:
      self.fontsize = int(os.getenv("FONTSIZE",fontsize))
    except Exception:
      self.fontsize = 120
    try:
      self.fontname = os.getenv("FONTNAME","Optima")
    except Exception:
      self.fontname = "Optima"
    self.bold = False
    self.italic = False
    self.font = QFont(self.fontname,self.fontsize)
    self.colors = [
        QColor(100,255,100),
        QColor(255,100,100),
        QColor(100,100,255),
        QColor(255,255,100),
        QColor(100,255,255),
        QColor(255,100,255)
    ]
    self.coloridx = 0
    self.ocolor = Qt.black
    self.pen = QPen(self.ocolor,self.fontsize/10)

    self.timer = QTimer(self)
    self.subtime = subtime
    self.timer.setInterval(self.subtime)

    screen = QGuiApplication.primaryScreen()
    geometry = screen.geometry()
    self.height = geometry.height()
    self.width = geometry.width()
    self.move(0,0)
    self.resize(self.width,self.height)

    self.inset = 40
    self.hh = self.height - 2*self.inset
    self.ww = self.width - 2*self.inset
    self.perimeter = (self.hh+self.ww)*2

    # window attributes
    self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.NoDropShadowWindowHint )
    self.setAttribute(Qt.WA_TranslucentBackground)

    self.timer.timeout.connect(self.tick)

    if surround:
      self.timer2 = QTimer(self)
      self.timer2.setInterval(1000/30)
      self.timer2.timeout.connect(self.tick2)

  def start(self):
    self.timer.start()
    if self.surround:
      self.timer2.start()
      self.tick2()

  def tick(self):
    try:
      self.index = (self.index + 1) % len(self.lines)
      self.coloridx = (self.coloridx + 1) % len(self.colors)
      self.hide()
      self.show()
      self.update()
    except KeyboardInterrupt:
      print("Ctrl-C")
      QApplication.instance().quit()

  def tick2(self):
    try:
      dt = self.timer.interval() - self.timer.remainingTime()
      dx = dt / self.subtime
      dx = max(dx,0.0)
      self.dx = dx
      self.dl = dx * self.perimeter
      self.update()
    except KeyboardInterrupt:
      print("Ctrl-C")
      QApplication.instance().quit()

  def mousePressEvent(self,event):
    QApplication.instance().quit()

  def paintEvent(self,event):
    with QPainter(self) as p:
      rect = self.rect()
      self.drawPerimeter(p)
      p.setFont(self.font)
      p.setPen(self.pen)
      text = self.lines[self.index].strip()
      metrics = QFontMetrics(self.font)
      brect = metrics.boundingRect(text)
      nlines = math.ceil((brect.width()*1.2)/self.width)
      words = re.split(r"\s+",text)
      h = metrics.height()
      i = 0
      curw = 0
      x = 50
      y = int(self.fontsize*1.5) 
      gap = int(self.fontsize * 0.5)

      for word in words:
        wrect = metrics.boundingRect(word) 
        if curw == 0:
          self.drawText(p,x,y+i*h,word)
          curw = wrect.width() + gap
          continue
        neww = curw + wrect.width()
        if neww >= self.width - 100:
          i += 1
          curw = 0
          self.drawText(p,x,y+i*h,word)
          curw = wrect.width() + gap
          continue
        self.drawText(p,x+curw,y+i*h,word)
        curw += wrect.width() + gap


  def drawPerimeter(self,p):
    if not self.surround:
      return
    i = self.inset
    w = self.ww
    w2 = w/2
    h = self.hh
    dl = self.dl
    dx = self.dx
    pe = self.perimeter

    color = QColor()
    color.setHslF(0.33*(1-dx),1.0,0.5)
    p.setPen(QPen(color,30))
    p.setBrush(Qt.NoBrush)
    path = QPainterPath()

    path.moveTo(i+w2,i)
    l = min(dl,w2)
    path.lineTo(i+w2+l,i)
    dl -= l
    if dl > 0:
      l = min(dl,h)
      path.lineTo(i+w,i+l)
      dl -= l
      if dl > 0:
        l = min(dl,w)
        path.lineTo(i+w-l,i+h)
        dl -= l
        if dl > 0:
          l = min(dl,h)
          path.lineTo(i,i+h-l)
          dl -= l
          if dl > 0:
            l = min(dl,w2)
            path.lineTo(i+l,i)

    p.drawPath(path)

  def drawText(self,p,x,y,text):
    path = QPainterPath()
    path.addText(x,y,self.font,text)

    # draw twice so that the black outline is _behind_ the fill
    p.setPen(self.pen)
    p.drawPath(path)
    p.setBrush(self.colors[self.coloridx])
    p.setPen(Qt.NoPen)
    p.drawPath(path)

Driver scripts

Larger font and perimeter snake

#!/usr/bin/env python3
import sys
import os
from setproctitle import setproctitle; setproctitle("sub1")
from subt import Subtitles as st
args = sys.argv[1:]
if len(args) == 0:
  if not os.path.exists("subt.txt"):
    print(f"{sys.argv[0]} subtitle_file.txt")
    print(f"use SUBTIME to set subtitle change period")
    exit(1)
  args = ["subt.txt"]
ifn = args[0]
if not os.path.isfile(ifn):
  print(f"{ifn} does not exist, or is not a file")
  exit(2)
with open(ifn) as f:
  src = f.read().rstrip().splitlines()

from PySide6.QtWidgets import QApplication
subtime = int(os.getenv("SUBTIME",30000))
app = QApplication(sys.argv)
thesubt = st(subtime,src)
thesubt.show()
thesubt.start()
try:
  exit(app.exec())
except KeyboardInterrupt:
  print("Ctrl-C 2")

Smaller font and no perimeter snake

#!/usr/bin/env python3
import sys
import os
from setproctitle import setproctitle; setproctitle("sub2")
from subt import Subtitles as st
args = sys.argv[1:]
if len(args) == 0:
  if not os.path.exists("subt.txt"):
    print(f"{sys.argv[0]} subtitle_file.txt")
    print(f"use SUBTIME to set subtitle change period")
    exit(1)
  args = ["subt.txt"]
ifn = args[0]
if not os.path.isfile(ifn):
  print(f"{ifn} does not exist, or is not a file")
  exit(2)
with open(ifn) as f:
  src = f.read().rstrip().splitlines()

from PySide6.QtWidgets import QApplication
subtime = int(os.getenv("SUBTIME",30000))
app = QApplication(sys.argv)
thesubt = st(subtime,src,surround=False,fontsize=40)
thesubt.show()
thesubt.start()
try:
  exit(app.exec())
except KeyboardInterrupt:
  print("Ctrl-C 2")