commit 86cefde130f1de2693a4a265efd271ca6a9e43c2 Author: Scheianu Ionut Date: Sun May 11 16:09:49 2025 +0300 ds-play created diff --git a/app/__pycache__/app.cpython-312.pyc b/app/__pycache__/app.cpython-312.pyc new file mode 100644 index 0000000..e22e1ee Binary files /dev/null and b/app/__pycache__/app.cpython-312.pyc differ diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..4fe6c75 --- /dev/null +++ b/app/app.py @@ -0,0 +1,223 @@ +from flask import Flask, jsonify, request, send_from_directory +import vlc +import os +import json +import requests +import logging + +# Configure logging +logging.basicConfig(level=logging.INFO) +Logger = logging.getLogger(__name__) + +app = Flask(__name__, static_folder='static') + +# VLC Media Player instance +vlc_instance = vlc.Instance() +player = vlc_instance.media_player_new() + +# File paths +PLAYLIST_FILE = './static/resurse/playlist.json' +APP_CONFIG_FILE = './app_config.json' +RESOURCES_FOLDER = './static/resurse' + +# Ensure the resources folder exists +os.makedirs(RESOURCES_FOLDER, exist_ok=True) + +# Load playlist +def load_playlist(): + if os.path.exists(PLAYLIST_FILE): + with open(PLAYLIST_FILE, 'r') as file: + return json.load(file) + return [] + +# Save playlist +def save_playlist(playlist): + with open(PLAYLIST_FILE, 'w') as file: + json.dump(playlist, file) + +# Load app configuration +def load_app_config(): + if os.path.exists(APP_CONFIG_FILE): + with open(APP_CONFIG_FILE, 'r') as file: + return json.load(file) + return { + "player_orientation": "portrait", + "player_name": "", + "quickconnect_code": "", + "server_address": "", + "port": 1025 + } + +# Save app configuration +def save_app_config(config): + with open(APP_CONFIG_FILE, 'w') as file: + json.dump(config, file) + +# Download media files +def download_media_files(playlist): + Logger.info("Starting media file download...") + for media in playlist: + file_name = media.get('file_name', '') + file_url = media.get('url', '') + file_path = os.path.join(RESOURCES_FOLDER, file_name) + + try: + response = requests.get(file_url) + if response.status_code == 200: + with open(file_path, 'wb') as file: + file.write(response.content) + Logger.info(f"Downloaded {file_name} to {file_path}") + else: + Logger.error(f"Failed to download {file_name}: {response.status_code}") + except requests.exceptions.RequestException as e: + Logger.error(f"Failed to download {file_name}: {e}") + +# Download playlist files from server +def download_playlist_files_from_server(): + Logger.info("Starting playlist file download using app configuration...") + + # Load app configuration + app_config = load_app_config() + server_address = app_config.get('server_address', '') + port = app_config.get('port', 1025) + hostname = app_config.get('player_name', '') + quickconnect_code = app_config.get('quickconnect_code', '') + + if not server_address or not hostname or not quickconnect_code: + Logger.error("Missing required configuration values.") + return + + # Construct the request URL and parameters + server_ip = f'{server_address}:{port}' + url = f'http://{server_ip}/api/playlists' + params = { + 'hostname': hostname, + 'quickconnect_code': quickconnect_code + } + + try: + # Send request to fetch the playlist + response = requests.get(url, params=params) + Logger.debug(f"Status Code: {response.status_code}") + Logger.debug(f"Response Content: {response.text}") + + if response.status_code == 200: + try: + playlist = response.json().get('playlist', []) + Logger.info("Playlist retrieved successfully.") + save_playlist(playlist) # Save the playlist locally + download_media_files(playlist) # Download media files + except json.JSONDecodeError as e: + Logger.error(f"Failed to parse playlist JSON: {e}") + else: + Logger.error(f"Failed to retrieve playlist: {response.text}") + except requests.exceptions.RequestException as e: + Logger.error(f"Failed to connect to server: {e}") + +@app.route('/') +def serve_index(): + """Serve the main index.html page.""" + return send_from_directory(app.static_folder, 'index.html') + +@app.route('/api/playlist', methods=['GET']) +def get_playlist(): + """Get the current playlist.""" + playlist = load_playlist() + return jsonify({'playlist': playlist}) + +@app.route('/api/playlist', methods=['POST']) +def update_playlist(): + """Update the playlist.""" + playlist = request.json.get('playlist', []) + save_playlist(playlist) + return jsonify({'status': 'success'}) + +@app.route('/api/play', methods=['POST']) +def play_media(): + """Play a media file.""" + file_path = request.json.get('file_path') + if not os.path.exists(file_path): + return jsonify({'error': 'File not found'}), 404 + + media = vlc_instance.media_new(file_path) + player.set_media(media) + player.play() + return jsonify({'status': 'playing', 'file': file_path}) + +@app.route('/api/stop', methods=['POST']) +def stop_media(): + """Stop media playback.""" + player.stop() + return jsonify({'status': 'stopped'}) + +@app.route('/api/config', methods=['GET']) +def get_config(): + """Get the app configuration.""" + config = load_app_config() + return jsonify(config) + +@app.route('/api/config', methods=['POST']) +def update_config(): + """Update the app configuration.""" + config = request.json + save_app_config(config) + return jsonify({'status': 'success'}) + +@app.route('/api/download_playlist', methods=['POST']) +def download_playlist(): + """Download playlist files from the server.""" + try: + download_playlist_files_from_server() + return jsonify({'status': 'success', 'message': 'Playlist files downloaded successfully.'}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}), 500 + +@app.route('/updated_playlist.json') +def serve_updated_playlist(): + """Serve the updated playlist file.""" + return send_from_directory('.', 'updated_playlist.json') + +def create_updated_playlist(): + """Create a new playlist file with local file paths.""" + Logger.info("Creating updated playlist with local file paths...") + + # Load the existing playlist + if not os.path.exists(PLAYLIST_FILE): + Logger.error(f"Playlist file not found: {PLAYLIST_FILE}") + return + + with open(PLAYLIST_FILE, 'r') as file: + playlist = json.load(file) + + # Update the playlist with local file paths + updated_playlist = [] + for media in playlist: + file_name = media.get('file_name', '') + local_path = f"/static/resurse/{file_name}" # Use Flask's static folder path + if os.path.exists(os.path.join(RESOURCES_FOLDER, file_name)): + updated_media = { + "type": "image" if file_name.lower().endswith(('.jpg', '.jpeg', '.png')) else "video", + "url": local_path, + "duration": media.get('duration', 0) # Keep the duration for images + } + updated_playlist.append(updated_media) + else: + Logger.warning(f"File not found in resurse folder: {file_name}") + + # Save the updated playlist to the root folder + updated_playlist_file = './updated_playlist.json' + with open(updated_playlist_file, 'w') as file: + json.dump(updated_playlist, file, indent=4) + + Logger.info(f"Updated playlist saved to {updated_playlist_file}") + +# Check and download playlist on app startup +def initialize_playlist(): + Logger.info("Initializing playlist...") + download_playlist_files_from_server() + Logger.info("Playlist initialization complete.") + +if __name__ == '__main__': + initialize_playlist() # Check and download playlist on startup + create_updated_playlist() # Create the updated playlist + app.run(host='0.0.0.0', port=1025) \ No newline at end of file diff --git a/app/app_config.json b/app/app_config.json new file mode 100644 index 0000000..be9d181 --- /dev/null +++ b/app/app_config.json @@ -0,0 +1 @@ +{"player_orientation": "portrait", "player_name": "tv-terasa", "quickconnect_code": "8887779", "server_address": "digi-signage.moto-adv.com", "port": 80} \ No newline at end of file diff --git a/app/install.sh b/app/install.sh new file mode 100644 index 0000000..9a708da --- /dev/null +++ b/app/install.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +echo "Updating package list..." +sudo apt update + +echo "Installing system dependencies..." +sudo apt install -y python3 python3-pip vlc libvlc-dev libvlccore-dev pulseaudio + +echo "Installing Python dependencies..." +pip3 install -r requirements.txt + +echo "Setup complete. You can now run the app using: python3 app.py or gunicorn -w 4 -b 0.0.0.0:1025 app:app" \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..02ef31f --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,4 @@ +Flask==2.3.2 +gunicorn==20.1.0 +python-vlc==3.0.18121 +requests==2.31.0 diff --git a/app/run_gunicorn.py b/app/run_gunicorn.py new file mode 100644 index 0000000..b04d770 --- /dev/null +++ b/app/run_gunicorn.py @@ -0,0 +1,5 @@ +import os +import sys + +if __name__ == "__main__": + os.system("gunicorn -w 4 -b 0.0.0.0:1025 app:app") \ No newline at end of file diff --git a/app/static/__pycache__/functions.cpython-312.pyc b/app/static/__pycache__/functions.cpython-312.pyc new file mode 100644 index 0000000..2391d09 Binary files /dev/null and b/app/static/__pycache__/functions.cpython-312.pyc differ diff --git a/app/static/functions.py b/app/static/functions.py new file mode 100644 index 0000000..b047265 --- /dev/null +++ b/app/static/functions.py @@ -0,0 +1,160 @@ +import os +import json +import requests +import logging + +# Configure logging +logging.basicConfig(level=logging.INFO) +Logger = logging.getLogger(__name__) + +def load_playlist(): + """Load playlist from the server or local storage.""" + try: + Logger.info("python_functions: Attempting to load playlist from server...") + + # Load app configuration + app_config_path = os.path.join(os.path.dirname(__file__), '../../app_config.json') + if not os.path.exists(app_config_path): + Logger.error("python_functions: App configuration file not found.") + return [] + + with open(app_config_path, 'r') as config_file: + app_config = json.load(config_file) + + server = app_config.get('server_address', '') + port = app_config.get('port', 1025) + host = app_config.get('player_name', '') + quick = app_config.get('quickconnect_code', '') + + if not server or not host or not quick: + Logger.error("python_functions: Missing required configuration values.") + return [] + + # Construct the server IP and request URL + server_ip = f'{server}:{port}' + url = f'http://{server_ip}/api/playlists' + params = { + 'hostname': host, + 'quickconnect_code': quick + } + + # Send the request + response = requests.get(url, params=params) + + # Debugging logs + Logger.debug(f"python_functions: Status Code: {response.status_code}") + Logger.debug(f"python_functions: Response Content: {response.text}") + + if response.status_code == 200: + try: + playlist = response.json().get('playlist', []) + Logger.info("python_functions: Playlist loaded successfully.") + Logger.debug(f"python_functions: Loaded playlist: {playlist}") + return playlist + except json.JSONDecodeError as e: + Logger.error(f"python_functions: Failed to parse JSON response: {e}") + else: + Logger.error(f"python_functions: Failed to retrieve playlist: {response.text}") + except requests.exceptions.RequestException as e: + Logger.error(f"python_functions: Failed to load playlist: {e}") + return [] + +def download_media_files(playlist): + """Download media files from the playlist.""" + Logger.info("python_functions: Starting media file download...") + base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') # Update this to the correct path + if not os.path.exists(base_dir): + os.makedirs(base_dir) + Logger.info(f"python_functions: Created directory {base_dir} for media files.") + + for media in playlist: + file_name = media.get('file_name', '') + file_url = media.get('url', '') + file_path = os.path.join(base_dir, file_name) + + try: + response = requests.get(file_url) + if response.status_code == 200: + with open(file_path, 'wb') as file: + file.write(response.content) + Logger.info(f"python_functions: Downloaded {file_name} to {file_path}") + else: + Logger.error(f"python_functions: Failed to download {file_name}: {response.status_code}") + except requests.exceptions.RequestException as e: + Logger.error(f"python_functions: Failed to download {file_name}: {e}") + +def download_playlist_files_from_server(): + """Download playlist files using app configuration.""" + Logger.info("python_functions: Starting playlist file download using app configuration...") + + # Load app configuration + app_config_path = os.path.join(os.path.dirname(__file__), '../../app_config.json') + if not os.path.exists(app_config_path): + Logger.error("python_functions: App configuration file not found.") + return + + try: + with open(app_config_path, 'r') as config_file: + app_config = json.load(config_file) + except json.JSONDecodeError as e: + Logger.error(f"python_functions: Failed to load app configuration: {e}") + return + + # Extract configuration values + server_address = app_config.get('server_address', '') + port = app_config.get('port', 1025) + hostname = app_config.get('player_name', '') + quickconnect_code = app_config.get('quickconnect_code', '') + + if not server_address or not hostname or not quickconnect_code: + Logger.error("python_functions: Missing required configuration values.") + return + + # Construct the request URL and parameters + server_ip = f'{server_address}:{port}' + url = f'http://{server_ip}/api/playlists' + params = { + 'hostname': hostname, + 'quickconnect_code': quickconnect_code + } + + try: + # Send request to fetch the playlist + response = requests.get(url, params=params) + Logger.debug(f"python_functions: Status Code: {response.status_code}") + Logger.debug(f"python_functions: Response Content: {response.text}") + + if response.status_code == 200: + try: + playlist = response.json().get('playlist', []) + Logger.info("python_functions: Playlist retrieved successfully.") + Logger.debug(f"python_functions: Playlist: {playlist}") + + # Download media files from the playlist + base_dir = os.path.join(os.path.dirname(__file__), 'static', 'resurse') + if not os.path.exists(base_dir): + os.makedirs(base_dir) + Logger.info(f"python_functions: Created directory {base_dir} for media files.") + + for media in playlist: + file_name = media.get('file_name', '') + file_url = media.get('url', '') + file_path = os.path.join(base_dir, file_name) + + try: + file_response = requests.get(file_url) + if file_response.status_code == 200: + with open(file_path, 'wb') as file: + file.write(file_response.content) + Logger.info(f"python_functions: Downloaded {file_name} to {file_path}") + else: + Logger.error(f"python_functions: Failed to download {file_name}: {file_response.status_code}") + except requests.exceptions.RequestException as e: + Logger.error(f"python_functions: Failed to download {file_name}: {e}") + except json.JSONDecodeError as e: + Logger.error(f"python_functions: Failed to parse playlist JSON: {e}") + else: + Logger.error(f"python_functions: Failed to retrieve playlist: {response.text}") + except requests.exceptions.RequestException as e: + Logger.error(f"python_functions: Failed to connect to server: {e}") +download_playlist_files_from_server() diff --git a/app/static/index.html b/app/static/index.html new file mode 100644 index 0000000..f152b7b --- /dev/null +++ b/app/static/index.html @@ -0,0 +1,182 @@ + + + + + + Media Player + + + + + +
+ +
+
+
+ + + + + + +
+
+ + + + \ No newline at end of file diff --git a/app/static/resurse/IMG_20250503_220547.jpg b/app/static/resurse/IMG_20250503_220547.jpg new file mode 100644 index 0000000..399582c Binary files /dev/null and b/app/static/resurse/IMG_20250503_220547.jpg differ diff --git a/app/static/resurse/IMG_20250506_080609.jpg b/app/static/resurse/IMG_20250506_080609.jpg new file mode 100644 index 0000000..dab400d Binary files /dev/null and b/app/static/resurse/IMG_20250506_080609.jpg differ diff --git a/app/static/resurse/VID_20250501_184228.mp4 b/app/static/resurse/VID_20250501_184228.mp4 new file mode 100644 index 0000000..842daf8 Binary files /dev/null and b/app/static/resurse/VID_20250501_184228.mp4 differ diff --git a/app/static/resurse/playlist.json b/app/static/resurse/playlist.json new file mode 100644 index 0000000..7c6bc07 --- /dev/null +++ b/app/static/resurse/playlist.json @@ -0,0 +1 @@ +[{"duration": 15, "file_name": "IMG_20250503_220547.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250503_220547.jpg"}, {"duration": 15, "file_name": "IMG_20250506_080609.jpg", "url": "http://digi-signage.moto-adv.com/media/IMG_20250506_080609.jpg"}, {"duration": 15, "file_name": "VID_20250501_184228.mp4", "url": "http://digi-signage.moto-adv.com/media/VID_20250501_184228.mp4"}] \ No newline at end of file diff --git a/app/static/settings.html b/app/static/settings.html new file mode 100644 index 0000000..60fe9fc --- /dev/null +++ b/app/static/settings.html @@ -0,0 +1,84 @@ + + + + + + Settings + + + +

Settings

+
+ + + + + + + + + + + + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/app/updated_playlist.json b/app/updated_playlist.json new file mode 100644 index 0000000..682edcd --- /dev/null +++ b/app/updated_playlist.json @@ -0,0 +1,17 @@ +[ + { + "type": "image", + "url": "/static/resurse/IMG_20250503_220547.jpg", + "duration": 15 + }, + { + "type": "image", + "url": "/static/resurse/IMG_20250506_080609.jpg", + "duration": 15 + }, + { + "type": "video", + "url": "/static/resurse/VID_20250501_184228.mp4", + "duration": 15 + } +] \ No newline at end of file