aaaabbbb
This commit is contained in:
158
signage_player/player.py
Normal file
158
signage_player/player.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import os
|
||||
import json
|
||||
import tkinter as tk
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'main_data', 'app_config.txt')
|
||||
PLAYLIST_DIR = os.path.join(os.path.dirname(__file__), 'static_data', 'playlist')
|
||||
MEDIA_DATA_PATH = os.path.join(os.path.dirname(__file__), 'static_data', 'media')
|
||||
|
||||
class SimpleTkPlayer:
|
||||
def __init__(self, root, playlist):
|
||||
self.root = root
|
||||
self.playlist = playlist
|
||||
self.current_index = 0
|
||||
self.paused = False
|
||||
self.pause_timer = None
|
||||
self.label = tk.Label(root, bg='black')
|
||||
self.label.pack(fill=tk.BOTH, expand=True)
|
||||
self.create_controls()
|
||||
self.hide_controls()
|
||||
self.root.bind('<Motion>', self.on_activity)
|
||||
self.root.bind('<Button-1>', self.on_activity)
|
||||
self.show_current_media()
|
||||
self.root.after(100, self.next_media_loop)
|
||||
|
||||
def create_controls(self):
|
||||
self.controls_frame = tk.Frame(self.root, bg='#222')
|
||||
self.controls_frame.place(relx=0.98, rely=0.98, anchor='se')
|
||||
self.prev_btn = tk.Button(self.controls_frame, text='⏮ Prev', command=self.prev_media, width=8)
|
||||
self.prev_btn.grid(row=0, column=0, padx=2)
|
||||
self.pause_btn = tk.Button(self.controls_frame, text='⏸ Pause', command=self.toggle_pause, width=8)
|
||||
self.pause_btn.grid(row=0, column=1, padx=2)
|
||||
self.next_btn = tk.Button(self.controls_frame, text='Next ⏭', command=self.next_media, width=8)
|
||||
self.next_btn.grid(row=0, column=2, padx=2)
|
||||
self.settings_btn = tk.Button(self.controls_frame, text='⚙ Settings', command=self.open_settings, width=10)
|
||||
self.settings_btn.grid(row=0, column=3, padx=2)
|
||||
self.exit_btn = tk.Button(self.controls_frame, text='⏻ Exit', command=self.exit_app, width=8, fg='red')
|
||||
self.exit_btn.grid(row=0, column=4, padx=2)
|
||||
|
||||
def show_controls(self):
|
||||
self.controls_frame.place(relx=0.98, rely=0.98, anchor='se')
|
||||
self.controls_frame.lift()
|
||||
self.schedule_hide_controls()
|
||||
|
||||
def hide_controls(self):
|
||||
self.controls_frame.place_forget()
|
||||
|
||||
def schedule_hide_controls(self):
|
||||
if hasattr(self, 'hide_controls_timer') and self.hide_controls_timer:
|
||||
self.root.after_cancel(self.hide_controls_timer)
|
||||
self.hide_controls_timer = self.root.after(5000, self.hide_controls)
|
||||
|
||||
def on_activity(self, event=None):
|
||||
self.show_controls()
|
||||
|
||||
def prev_media(self):
|
||||
self.current_index = (self.current_index - 1) % len(self.playlist)
|
||||
self.show_current_media()
|
||||
|
||||
def next_media(self):
|
||||
self.current_index = (self.current_index + 1) % len(self.playlist)
|
||||
self.show_current_media()
|
||||
|
||||
def toggle_pause(self):
|
||||
if not self.paused:
|
||||
self.paused = True
|
||||
self.pause_btn.config(text='▶ Resume')
|
||||
self.pause_timer = self.root.after(30000, self.resume_play)
|
||||
else:
|
||||
self.resume_play()
|
||||
|
||||
def resume_play(self):
|
||||
self.paused = False
|
||||
self.pause_btn.config(text='⏸ Pause')
|
||||
if self.pause_timer:
|
||||
self.root.after_cancel(self.pause_timer)
|
||||
self.pause_timer = None
|
||||
|
||||
def show_current_media(self):
|
||||
if not self.playlist:
|
||||
self.label.config(text="No media available", fg='white', font=('Arial', 32))
|
||||
return
|
||||
media = self.playlist[self.current_index]
|
||||
file_path = os.path.join(MEDIA_DATA_PATH, media['file_name'])
|
||||
if file_path.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')):
|
||||
try:
|
||||
img = Image.open(file_path)
|
||||
# Fit to screen without crop or stretch
|
||||
screen_w = self.root.winfo_screenwidth()
|
||||
screen_h = self.root.winfo_screenheight()
|
||||
img_w, img_h = img.size
|
||||
scale = min(screen_w / img_w, screen_h / img_h)
|
||||
new_w = int(img_w * scale)
|
||||
new_h = int(img_h * scale)
|
||||
img = img.resize((new_w, new_h), Image.LANCZOS)
|
||||
bg = Image.new('RGB', (screen_w, screen_h), 'black')
|
||||
x = (screen_w - new_w) // 2
|
||||
y = (screen_h - new_h) // 2
|
||||
bg.paste(img, (x, y))
|
||||
photo = ImageTk.PhotoImage(bg)
|
||||
self.label.config(image=photo, text='')
|
||||
self.label.image = photo
|
||||
except Exception as e:
|
||||
self.label.config(text=f"Image error: {e}", fg='red')
|
||||
else:
|
||||
self.label.config(text=f"Unsupported: {media['file_name']}", fg='yellow')
|
||||
|
||||
def next_media_loop(self):
|
||||
if not self.playlist or self.paused:
|
||||
self.root.after(1000, self.next_media_loop)
|
||||
return
|
||||
duration = self.playlist[self.current_index].get('duration', 10)
|
||||
self.current_index = (self.current_index + 1) % len(self.playlist)
|
||||
self.show_current_media()
|
||||
self.root.after(duration * 1000, self.next_media_loop)
|
||||
|
||||
def exit_app(self):
|
||||
self.root.destroy()
|
||||
|
||||
def open_settings(self):
|
||||
if self.paused is not True:
|
||||
self.paused = True
|
||||
self.pause_btn.config(text='▶ Resume')
|
||||
settings_win = tk.Toplevel(self.root)
|
||||
settings_win.title('Settings')
|
||||
settings_win.geometry('400x300+100+100')
|
||||
settings_win.transient(self.root)
|
||||
settings_win.grab_set()
|
||||
tk.Label(settings_win, text='Settings', font=('Arial', 18)).pack(pady=10)
|
||||
# Example setting: close button
|
||||
tk.Button(settings_win, text='Close', command=settings_win.destroy).pack(pady=20)
|
||||
def on_close():
|
||||
settings_win.grab_release()
|
||||
settings_win.destroy()
|
||||
self.resume_play()
|
||||
settings_win.protocol('WM_DELETE_WINDOW', on_close)
|
||||
settings_win.bind('<Destroy>', lambda e: self.resume_play() if not settings_win.winfo_exists() else None)
|
||||
|
||||
def load_latest_playlist():
|
||||
files = [f for f in os.listdir(PLAYLIST_DIR) if f.startswith('server_playlist_v') and f.endswith('.json')]
|
||||
if not files:
|
||||
return []
|
||||
files.sort(key=lambda x: int(x.split('_v')[-1].split('.json')[0]), reverse=True)
|
||||
with open(os.path.join(PLAYLIST_DIR, files[0]), 'r') as f:
|
||||
data = json.load(f)
|
||||
return data.get('playlist', [])
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
root.title("Simple Tkinter Player")
|
||||
root.configure(bg='black')
|
||||
root.attributes('-fullscreen', True)
|
||||
playlist = load_latest_playlist()
|
||||
player = SimpleTkPlayer(root, playlist)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user