diff --git a/signage_player/__pycache__/get_playlists.cpython-311.pyc b/signage_player/__pycache__/get_playlists.cpython-311.pyc index 5f5622c..9219dd0 100644 Binary files a/signage_player/__pycache__/get_playlists.cpython-311.pyc and b/signage_player/__pycache__/get_playlists.cpython-311.pyc differ diff --git a/signage_player/__pycache__/player.cpython-311.pyc b/signage_player/__pycache__/player.cpython-311.pyc new file mode 100644 index 0000000..bdecfb2 Binary files /dev/null and b/signage_player/__pycache__/player.cpython-311.pyc differ diff --git a/signage_player/get_playlists.py b/signage_player/get_playlists.py index 7c78fea..a7184ae 100644 --- a/signage_player/get_playlists.py +++ b/signage_player/get_playlists.py @@ -105,6 +105,38 @@ def download_media_files(playlist, media_dir): updated_playlist.append(updated_media) return updated_playlist +def delete_old_playlists_and_media(current_version, playlist_dir, media_dir, keep_versions=1): + """ + Delete old playlist files and media files not referenced by the latest playlist version. + keep_versions: number of latest versions to keep (default 1) + """ + # Find all playlist files + playlist_files = [f for f in os.listdir(playlist_dir) if f.startswith('server_playlist_v') and f.endswith('.json')] + # Keep only the latest N versions + versions = sorted([int(f.split('_v')[-1].split('.json')[0]) for f in playlist_files], reverse=True) + keep = set(versions[:keep_versions]) + # Delete old playlist files + for f in playlist_files: + v = int(f.split('_v')[-1].split('.json')[0]) + if v not in keep: + os.remove(os.path.join(playlist_dir, f)) + # Collect all media files referenced by the kept playlists + referenced = set() + for v in keep: + path = os.path.join(playlist_dir, f'server_playlist_v{v}.json') + if os.path.exists(path): + with open(path, 'r') as f: + data = json.load(f) + for item in data.get('playlist', []): + referenced.add(item.get('file_name')) + # Delete media files not referenced + for f in os.listdir(media_dir): + if f not in referenced: + try: + os.remove(os.path.join(media_dir, f)) + except Exception as e: + Logger.warning(f"Failed to delete media file {f}: {e}") + def update_playlist_if_needed(local_playlist_path, config, media_dir, playlist_dir): """ Fetch the server playlist once, compare versions, and update if needed. @@ -125,6 +157,8 @@ def update_playlist_if_needed(local_playlist_path, config, media_dir, playlist_d updated_playlist = download_media_files(server_data['playlist'], media_dir) server_data['playlist'] = updated_playlist save_playlist_with_version(server_data, playlist_dir) + # Delete old playlists and unreferenced media + delete_old_playlists_and_media(server_version, playlist_dir, media_dir) return True else: Logger.warning("No playlist data fetched from server or playlist is empty.") @@ -135,4 +169,3 @@ def update_playlist_if_needed(local_playlist_path, config, media_dir, playlist_d - diff --git a/signage_player/main.py b/signage_player/main.py index f20f1df..b7d35b6 100644 --- a/signage_player/main.py +++ b/signage_player/main.py @@ -9,8 +9,8 @@ MEDIA_DATA_PATH = os.path.join(os.path.dirname(__file__), 'static_data', 'media' PLAYLIST_DIR = os.path.join(os.path.dirname(__file__), 'static_data', 'playlist') LOCAL_PLAYLIST_PATH = os.path.join(PLAYLIST_DIR, 'server_playlist.json') -def playlist_update_loop(refresh_time, stop_event, config, local_playlist_path, media_dir, playlist_dir): - while not stop_event.is_set(): +def playlist_update_loop(refresh_time, app_running, config, local_playlist_path, media_dir, playlist_dir): + while app_running[0]: updated = update_playlist_if_needed(local_playlist_path, config, media_dir, playlist_dir) if updated: print(f"[REFRESH] Playlist updated from server at {time.strftime('%X')}") @@ -18,24 +18,54 @@ def playlist_update_loop(refresh_time, stop_event, config, local_playlist_path, print(f"[REFRESH] Playlist already up to date at {time.strftime('%X')}") time.sleep(refresh_time) + +import tkinter as tk +from player import SimpleTkPlayer, load_latest_playlist + def main(): with open(CONFIG_PATH, 'r') as f: config = json.load(f) refresh_time = int(config.get('refresh_time', 5)) * 60 # minutes - stop_event = threading.Event() + app_running = [True] update_thread = threading.Thread( target=playlist_update_loop, - args=(refresh_time, stop_event, config, LOCAL_PLAYLIST_PATH, MEDIA_DATA_PATH, PLAYLIST_DIR), + args=(refresh_time, app_running, config, LOCAL_PLAYLIST_PATH, MEDIA_DATA_PATH, PLAYLIST_DIR), daemon=True ) update_thread.start() - print("Playlist update thread started. Press Ctrl+C to exit.") + + root = tk.Tk() + root.title("Simple Tkinter Player") + root.configure(bg='black') + root.attributes('-fullscreen', True) + + # Load playlist and create player + playlist = load_latest_playlist() + player = SimpleTkPlayer(root, playlist) + player.app_running = app_running + orig_exit_app = player.exit_app + def exit_and_stop(): + app_running[0] = False + orig_exit_app() + player.exit_app = exit_and_stop + player.exit_btn.config(command=player.exit_app) + player.main_start() + + def reload_playlist_if_updated(): + new_playlist = load_latest_playlist() + if new_playlist != player.playlist: + player.playlist = new_playlist + player.current_index = 0 + player.show_current_media() + root.after(10000, reload_playlist_if_updated) + reload_playlist_if_updated() + try: - while True: - time.sleep(1) + root.mainloop() except KeyboardInterrupt: - print("Exiting...") - stop_event.set() + pass + finally: + app_running[0] = False update_thread.join() if __name__ == '__main__': diff --git a/signage_player/main_data/app_config.txt b/signage_player/main_data/app_config.txt index 6639942..a27e789 100644 --- a/signage_player/main_data/app_config.txt +++ b/signage_player/main_data/app_config.txt @@ -2,8 +2,8 @@ "screen_orientation": "Landscape", "screen_name": "tv-terasa", "quickconnect_key": "8887779", - "server_ip": "digi-signage.moto-adv.com", - "port": "8880", + "server_ip": "192.168.1.22", + "port": "80", "screen_w": "1920", "screen_h": "1080", "refresh_time": "5" diff --git a/signage_player/main_data/intro1.mp4 b/signage_player/main_data/intro1.mp4 new file mode 100644 index 0000000..c522a56 Binary files /dev/null and b/signage_player/main_data/intro1.mp4 differ diff --git a/signage_player/main_data/log.txt b/signage_player/main_data/log.txt index 8f385ae..6eda3ad 100644 --- a/signage_player/main_data/log.txt +++ b/signage_player/main_data/log.txt @@ -20,3 +20,176 @@ [INFO] [SignageApp] Successfully downloaded trans_cindrel_4.jpg to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/trans_cindrel_4.jpg [INFO] [SignageApp] Preparing to download 101394-video-1080.mp4 from http://digi-signage.moto-adv.com/media/101394-video-1080.mp4... [ERROR] [SignageApp] Failed to download 101394-video-1080.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://digi-signage.moto-adv.com/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 502 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.22', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:8880/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.22', port=8880): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 2} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 2} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 2 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] Successfully downloaded call-of-duty-black-3840x2160-23674.jpg to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/call-of-duty-black-3840x2160-23674.jpg +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[ERROR] [SignageApp] Failed to download big-buck-bunny-1080p-60fps-30sec.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 2} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 2 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[ERROR] [SignageApp] Failed to download big-buck-bunny-1080p-60fps-30sec.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 2} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 2 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] Successfully downloaded call-of-duty-black-3840x2160-23674.jpg to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/call-of-duty-black-3840x2160-23674.jpg +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[ERROR] [SignageApp] Failed to download big-buck-bunny-1080p-60fps-30sec.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 2} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 2 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[ERROR] [SignageApp] Failed to download big-buck-bunny-1080p-60fps-30sec.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 10, 'file_name': 'one-piece-season-2-5120x2880-23673.jpg', 'url': 'http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 3} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 3 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download one-piece-season-2-5120x2880-23673.jpg from http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg... +[INFO] [SignageApp] Successfully downloaded one-piece-season-2-5120x2880-23673.jpg to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/one-piece-season-2-5120x2880-23673.jpg +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[ERROR] [SignageApp] Failed to download big-buck-bunny-1080p-60fps-30sec.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.22', port=80): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.22', port=80): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.22', port=80): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist: HTTPConnectionPool(host='192.168.1.22', port=80): Max retries exceeded with url: /api/playlists?hostname=tv-terasa&quickconnect_code=8887779 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 111] Connection refused')) +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 10, 'file_name': 'one-piece-season-2-5120x2880-23673.jpg', 'url': 'http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 3} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 3 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download one-piece-season-2-5120x2880-23673.jpg from http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg... +[INFO] [SignageApp] File one-piece-season-2-5120x2880-23673.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[ERROR] [SignageApp] Failed to download big-buck-bunny-1080p-60fps-30sec.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$YL2Id5c4CT5yE31KlK0ZF.mK033OimRNKbMR2ehTpGLFyPmDxnEOy', 'playlist': [{'duration': 20, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 10, 'file_name': 'one-piece-season-2-5120x2880-23673.jpg', 'url': 'http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}], 'playlist_version': 3} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 3 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download one-piece-season-2-5120x2880-23673.jpg from http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg... +[INFO] [SignageApp] File one-piece-season-2-5120x2880-23673.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[ERROR] [SignageApp] Failed to download big-buck-bunny-1080p-60fps-30sec.mp4. Status Code: 404 +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[ERROR] [SignageApp] Failed to fetch playlist. Status Code: 404 +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 0 +[INFO] [SignageApp] Local playlist is already up to date. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$4dxmxuyiezoojRSThjmzQeVsaBscU5vP.9GcTPJhXymmL9JsNklea', 'playlist': [{'duration': 10, 'file_name': 'one-piece-season-2-5120x2880-23673.jpg', 'url': 'http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}, {'duration': 10, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}], 'playlist_version': 2} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 2 +[INFO] [SignageApp] Preparing to download one-piece-season-2-5120x2880-23673.jpg from http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg... +[INFO] [SignageApp] File one-piece-season-2-5120x2880-23673.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[INFO] [SignageApp] Successfully downloaded big-buck-bunny-1080p-60fps-30sec.mp4 to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/big-buck-bunny-1080p-60fps-30sec.mp4 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$4dxmxuyiezoojRSThjmzQeVsaBscU5vP.9GcTPJhXymmL9JsNklea', 'playlist': [{'duration': 10, 'file_name': 'one-piece-season-2-5120x2880-23673.jpg', 'url': 'http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}, {'duration': 10, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}], 'playlist_version': 2} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 2 +[INFO] [SignageApp] Preparing to download one-piece-season-2-5120x2880-23673.jpg from http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg... +[INFO] [SignageApp] File one-piece-season-2-5120x2880-23673.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[INFO] [SignageApp] Successfully downloaded big-buck-bunny-1080p-60fps-30sec.mp4 to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/big-buck-bunny-1080p-60fps-30sec.mp4 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$4dxmxuyiezoojRSThjmzQeVsaBscU5vP.9GcTPJhXymmL9JsNklea', 'playlist': [{'duration': 10, 'file_name': 'one-piece-season-2-5120x2880-23673.jpg', 'url': 'http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}, {'duration': 10, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 10, 'file_name': 'demo2.jpeg', 'url': 'http://192.168.1.22/media/demo2.jpeg'}], 'playlist_version': 3} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 3 +[INFO] [SignageApp] Preparing to download one-piece-season-2-5120x2880-23673.jpg from http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg... +[INFO] [SignageApp] File one-piece-season-2-5120x2880-23673.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[INFO] [SignageApp] Successfully downloaded big-buck-bunny-1080p-60fps-30sec.mp4 to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/big-buck-bunny-1080p-60fps-30sec.mp4 +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download demo2.jpeg from http://192.168.1.22/media/demo2.jpeg... +[INFO] [SignageApp] Successfully downloaded demo2.jpeg to /home/pi/Desktop/tkinter_player/signage_player/static_data/media/demo2.jpeg +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$4dxmxuyiezoojRSThjmzQeVsaBscU5vP.9GcTPJhXymmL9JsNklea', 'playlist': [{'duration': 10, 'file_name': 'one-piece-season-2-5120x2880-23673.jpg', 'url': 'http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg'}, {'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}, {'duration': 10, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 10, 'file_name': 'demo2.jpeg', 'url': 'http://192.168.1.22/media/demo2.jpeg'}], 'playlist_version': 3} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 3 +[INFO] [SignageApp] Preparing to download one-piece-season-2-5120x2880-23673.jpg from http://192.168.1.22/media/one-piece-season-2-5120x2880-23673.jpg... +[INFO] [SignageApp] File one-piece-season-2-5120x2880-23673.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[INFO] [SignageApp] File big-buck-bunny-1080p-60fps-30sec.mp4 already exists. Skipping download. +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download demo2.jpeg from http://192.168.1.22/media/demo2.jpeg... +[INFO] [SignageApp] File demo2.jpeg already exists. Skipping download. +[INFO] [SignageApp] Fetching playlist from URL: http://192.168.1.22:80/api/playlists with params: {'hostname': 'tv-terasa', 'quickconnect_code': '8887779'} +[INFO] [SignageApp] Server response: {'hashed_quickconnect': '$2b$12$4dxmxuyiezoojRSThjmzQeVsaBscU5vP.9GcTPJhXymmL9JsNklea', 'playlist': [{'duration': 30, 'file_name': 'big-buck-bunny-1080p-60fps-30sec.mp4', 'url': 'http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4'}, {'duration': 10, 'file_name': 'call-of-duty-black-3840x2160-23674.jpg', 'url': 'http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg'}, {'duration': 10, 'file_name': 'demo2.jpeg', 'url': 'http://192.168.1.22/media/demo2.jpeg'}], 'playlist_version': 4} +[INFO] [SignageApp] Fetched updated playlist from server. +[INFO] [SignageApp] Local playlist version: 0, Server playlist version: 4 +[INFO] [SignageApp] Preparing to download big-buck-bunny-1080p-60fps-30sec.mp4 from http://192.168.1.22/media/big-buck-bunny-1080p-60fps-30sec.mp4... +[INFO] [SignageApp] File big-buck-bunny-1080p-60fps-30sec.mp4 already exists. Skipping download. +[INFO] [SignageApp] Preparing to download call-of-duty-black-3840x2160-23674.jpg from http://192.168.1.22/media/call-of-duty-black-3840x2160-23674.jpg... +[INFO] [SignageApp] File call-of-duty-black-3840x2160-23674.jpg already exists. Skipping download. +[INFO] [SignageApp] Preparing to download demo2.jpeg from http://192.168.1.22/media/demo2.jpeg... +[INFO] [SignageApp] File demo2.jpeg already exists. Skipping download. diff --git a/signage_player/player copy.py b/signage_player/player copy.py new file mode 100644 index 0000000..4adc360 --- /dev/null +++ b/signage_player/player copy.py @@ -0,0 +1,224 @@ +import os +import json +import tkinter as tk +from PIL import Image, ImageTk +import vlc + +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('', self.on_activity) + self.root.bind('', self.on_activity) + self.root.after(100, self.ensure_fullscreen) + + def ensure_fullscreen(self): + self.root.attributes('-fullscreen', True) + self.root.update_idletasks() + + def create_controls(self): + self.controls_frame = tk.Frame(self.root, bg='#222', bd=2, relief='ridge') + self.controls_frame.place(relx=0.98, rely=0.98, anchor='se') + btn_style = { + 'bg': '#333', + 'fg': 'white', + 'activebackground': '#555', + 'activeforeground': '#00e6e6', + 'font': ('Arial', 16, 'bold'), + 'bd': 0, + 'highlightthickness': 0, + 'relief': 'flat', + 'cursor': 'hand2', + 'padx': 10, + 'pady': 6 + } + self.prev_btn = tk.Button(self.controls_frame, text='⏮ Prev', command=self.prev_media, **btn_style) + self.prev_btn.grid(row=0, column=0, padx=4) + self.pause_btn = tk.Button(self.controls_frame, text='⏸ Pause', command=self.toggle_pause, **btn_style) + self.pause_btn.grid(row=0, column=1, padx=4) + self.next_btn = tk.Button(self.controls_frame, text='Next ⏭', command=self.next_media, **btn_style) + self.next_btn.grid(row=0, column=2, padx=4) + self.settings_btn = tk.Button(self.controls_frame, text='⚙ Settings', command=self.open_settings, **btn_style) + self.settings_btn.grid(row=0, column=3, padx=4) + self.exit_btn = tk.Button(self.controls_frame, text='⏻ Exit', command=self.exit_app, **btn_style) + self.exit_btn.grid(row=0, column=4, padx=4) + self.exit_btn.config(fg='#ff4d4d') + + 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 play_intro_video(self): + intro_path = os.path.join(os.path.dirname(__file__), 'main_data', 'intro1.mp4') + if os.path.exists(intro_path): + self.show_video(intro_path, on_end=self.after_intro) + else: + self.after_intro() + + def after_intro(self): + self.show_current_media() + self.root.after(100, self.next_media_loop) + + def show_video(self, file_path, on_end=None): + if hasattr(self, 'vlc_player') and self.vlc_player: + self.vlc_player.stop() + if not hasattr(self, 'video_canvas'): + self.video_canvas = tk.Canvas(self.root, bg='black', highlightthickness=0) + self.video_canvas.pack(fill=tk.BOTH, expand=True) + self.label.pack_forget() + self.video_canvas.pack(fill=tk.BOTH, expand=True) + self.root.attributes('-fullscreen', True) + self.root.update_idletasks() + self.vlc_instance = vlc.Instance() + self.vlc_player = self.vlc_instance.media_player_new() + self.vlc_player.set_mrl(file_path) + self.vlc_player.set_fullscreen(True) + self.vlc_player.set_xwindow(self.video_canvas.winfo_id()) + self.vlc_player.play() + def check_end(): + if self.vlc_player.get_state() == vlc.State.Ended: + self.video_canvas.pack_forget() + self.label.pack(fill=tk.BOTH, expand=True) + if on_end: + on_end() + else: + self.root.after(200, check_end) + check_end() + + def show_current_media(self): + self.root.attributes('-fullscreen', True) + self.root.update_idletasks() + 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(('.mp4', '.avi', '.mov', '.mkv')): + self.show_video(file_path, on_end=self.next_media) + elif 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): + # Signal the update thread to stop if stop_event is present + if hasattr(self, 'stop_event') and self.stop_event: + self.stop_event.set() + 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('', lambda e: self.resume_play() if not settings_win.winfo_exists() else None) + + def main_start(self): + self.play_intro_video() + +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) + player.main_start() + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/signage_player/player.py b/signage_player/player.py index 57cf958..ee838b2 100644 --- a/signage_player/player.py +++ b/signage_player/player.py @@ -2,6 +2,7 @@ import os import json import tkinter as tk from PIL import Image, ImageTk +import vlc 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') @@ -20,30 +21,86 @@ class SimpleTkPlayer: self.hide_controls() self.root.bind('', self.on_activity) self.root.bind('', self.on_activity) - self.show_current_media() - self.root.after(100, self.next_media_loop) + self.root.after(100, self.ensure_fullscreen) + self.root.after(200, self.hide_mouse) + self.root.after(300, self.move_mouse_to_corner) + self.root.protocol('WM_DELETE_WINDOW', self.exit_app) + + def ensure_fullscreen(self): + self.root.attributes('-fullscreen', True) + self.root.update_idletasks() 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) + # Create a transparent, borderless top-level window for controls + self.controls_win = tk.Toplevel(self.root) + self.controls_win.overrideredirect(True) + self.controls_win.attributes('-topmost', True) + self.controls_win.attributes('-alpha', 0.92) + self.controls_win.configure(bg='') + # Place the window at the bottom right + def place_controls(): + self.controls_win.update_idletasks() + w = self.controls_win.winfo_reqwidth() + h = self.controls_win.winfo_reqheight() + sw = self.root.winfo_screenwidth() + sh = self.root.winfo_screenheight() + x = sw - w - 30 + y = sh - h - 30 + self.controls_win.geometry(f'+{x}+{y}') + self.controls_frame = tk.Frame(self.controls_win, bg='#222', bd=2, relief='ridge') + self.controls_frame.pack() + btn_style = { + 'bg': '#333', + 'fg': 'white', + 'activebackground': '#555', + 'activeforeground': '#00e6e6', + 'font': ('Arial', 16, 'bold'), + 'bd': 0, + 'highlightthickness': 0, + 'relief': 'flat', + 'cursor': 'hand2', + 'padx': 10, + 'pady': 6 + } + self.prev_btn = tk.Button(self.controls_frame, text='⏮ Prev', command=self.prev_media, **btn_style) + self.prev_btn.grid(row=0, column=0, padx=4) + self.pause_btn = tk.Button(self.controls_frame, text='⏸ Pause', command=self.toggle_pause, **btn_style) + self.pause_btn.grid(row=0, column=1, padx=4) + self.next_btn = tk.Button(self.controls_frame, text='Next ⏭', command=self.next_media, **btn_style) + self.next_btn.grid(row=0, column=2, padx=4) + self.settings_btn = tk.Button(self.controls_frame, text='⚙ Settings', command=self.open_settings, **btn_style) + self.settings_btn.grid(row=0, column=3, padx=4) + self.exit_btn = tk.Button(self.controls_frame, text='⏻ Exit', command=self.exit_app, **btn_style) + self.exit_btn.grid(row=0, column=4, padx=4) + self.exit_btn.config(fg='#ff4d4d') + self.controls_win.withdraw() + self.controls_win.after(200, place_controls) + self.root.bind('', lambda e: self.controls_win.after(200, place_controls)) + + def hide_mouse(self): + self.root.config(cursor='none') + + def show_mouse(self): + self.root.config(cursor='arrow') + + def move_mouse_to_corner(self): + try: + import pyautogui + sw = self.root.winfo_screenwidth() + sh = self.root.winfo_screenheight() + pyautogui.moveTo(sw-2, sh-2) + except Exception: + pass def show_controls(self): - self.controls_frame.place(relx=0.98, rely=0.98, anchor='se') - self.controls_frame.lift() + self.controls_win.deiconify() + self.controls_win.lift() + self.show_mouse() self.schedule_hide_controls() def hide_controls(self): - self.controls_frame.place_forget() + self.controls_win.withdraw() + self.hide_mouse() def schedule_hide_controls(self): if hasattr(self, 'hide_controls_timer') and self.hide_controls_timer: @@ -76,13 +133,54 @@ class SimpleTkPlayer: self.root.after_cancel(self.pause_timer) self.pause_timer = None + def play_intro_video(self): + intro_path = os.path.join(os.path.dirname(__file__), 'main_data', 'intro1.mp4') + if os.path.exists(intro_path): + self.show_video(intro_path, on_end=self.after_intro) + else: + self.after_intro() + + def after_intro(self): + self.show_current_media() + self.root.after(100, self.next_media_loop) + + def show_video(self, file_path, on_end=None): + if hasattr(self, 'vlc_player') and self.vlc_player: + self.vlc_player.stop() + if not hasattr(self, 'video_canvas'): + self.video_canvas = tk.Canvas(self.root, bg='black', highlightthickness=0) + self.video_canvas.pack(fill=tk.BOTH, expand=True) + self.label.pack_forget() + self.video_canvas.pack(fill=tk.BOTH, expand=True) + self.root.attributes('-fullscreen', True) + self.root.update_idletasks() + self.vlc_instance = vlc.Instance() + self.vlc_player = self.vlc_instance.media_player_new() + self.vlc_player.set_mrl(file_path) + self.vlc_player.set_fullscreen(True) + self.vlc_player.set_xwindow(self.video_canvas.winfo_id()) + self.vlc_player.play() + def check_end(): + if self.vlc_player.get_state() == vlc.State.Ended: + self.video_canvas.pack_forget() + self.label.pack(fill=tk.BOTH, expand=True) + if on_end: + on_end() + else: + self.root.after(200, check_end) + check_end() + def show_current_media(self): + self.root.attributes('-fullscreen', True) + self.root.update_idletasks() 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')): + if file_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')): + self.show_video(file_path, on_end=self.next_media) + elif file_path.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp', '.gif')): try: img = Image.open(file_path) # Fit to screen without crop or stretch @@ -115,7 +213,20 @@ class SimpleTkPlayer: self.root.after(duration * 1000, self.next_media_loop) def exit_app(self): - self.root.destroy() + # Signal the update thread to stop if stop_event is present + if hasattr(self, 'stop_event') and self.stop_event: + self.stop_event.set() + if hasattr(self, 'app_running') and self.app_running: + self.app_running[0] = False + try: + if hasattr(self, 'controls_win') and self.controls_win: + self.controls_win.destroy() + except: + pass + try: + self.root.destroy() + except: + pass def open_settings(self): if self.paused is not True: @@ -136,6 +247,9 @@ class SimpleTkPlayer: settings_win.protocol('WM_DELETE_WINDOW', on_close) settings_win.bind('', lambda e: self.resume_play() if not settings_win.winfo_exists() else None) + def main_start(self): + self.play_intro_video() + 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: @@ -152,6 +266,7 @@ def main(): root.attributes('-fullscreen', True) playlist = load_latest_playlist() player = SimpleTkPlayer(root, playlist) + player.main_start() root.mainloop() if __name__ == '__main__': diff --git a/signage_player/static_data/media/Cindrel_1.jpg b/signage_player/static_data/media/Cindrel_1.jpg deleted file mode 100644 index 2579223..0000000 Binary files a/signage_player/static_data/media/Cindrel_1.jpg and /dev/null differ diff --git a/signage_player/static_data/media/big-buck-bunny-1080p-60fps-30sec.mp4 b/signage_player/static_data/media/big-buck-bunny-1080p-60fps-30sec.mp4 new file mode 100644 index 0000000..2edb1cb Binary files /dev/null and b/signage_player/static_data/media/big-buck-bunny-1080p-60fps-30sec.mp4 differ diff --git a/signage_player/static_data/media/call-of-duty-black-3840x2160-23674.jpg b/signage_player/static_data/media/call-of-duty-black-3840x2160-23674.jpg new file mode 100644 index 0000000..62f7d98 Binary files /dev/null and b/signage_player/static_data/media/call-of-duty-black-3840x2160-23674.jpg differ diff --git a/signage_player/static_data/media/demo2.jpeg b/signage_player/static_data/media/demo2.jpeg new file mode 100644 index 0000000..29d8372 Binary files /dev/null and b/signage_player/static_data/media/demo2.jpeg differ diff --git a/signage_player/static_data/media/trans_cindrel_4.jpg b/signage_player/static_data/media/trans_cindrel_4.jpg deleted file mode 100644 index 6897121..0000000 Binary files a/signage_player/static_data/media/trans_cindrel_4.jpg and /dev/null differ diff --git a/signage_player/static_data/playlist/server_playlist_v30.json b/signage_player/static_data/playlist/server_playlist_v30.json deleted file mode 100644 index 285da5d..0000000 --- a/signage_player/static_data/playlist/server_playlist_v30.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "playlist": [ - { - "file_name": "Cindrel_1.jpg", - "url": "media/Cindrel_1.jpg", - "duration": 10 - }, - { - "file_name": "trans_cindrel_4.jpg", - "url": "media/trans_cindrel_4.jpg", - "duration": 10 - } - ], - "version": 30 -} \ No newline at end of file diff --git a/signage_player/static_data/playlist/server_playlist_v4.json b/signage_player/static_data/playlist/server_playlist_v4.json new file mode 100644 index 0000000..78da892 --- /dev/null +++ b/signage_player/static_data/playlist/server_playlist_v4.json @@ -0,0 +1,20 @@ +{ + "playlist": [ + { + "file_name": "big-buck-bunny-1080p-60fps-30sec.mp4", + "url": "media/big-buck-bunny-1080p-60fps-30sec.mp4", + "duration": 30 + }, + { + "file_name": "call-of-duty-black-3840x2160-23674.jpg", + "url": "media/call-of-duty-black-3840x2160-23674.jpg", + "duration": 10 + }, + { + "file_name": "demo2.jpeg", + "url": "media/demo2.jpeg", + "duration": 10 + } + ], + "version": 4 +} \ No newline at end of file