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