365 lines
14 KiB
Python
Executable File
365 lines
14 KiB
Python
Executable File
from flask import Flask, jsonify, request, send_from_directory
|
|
import vlc
|
|
import os
|
|
import json
|
|
import requests
|
|
import logging
|
|
import threading
|
|
import time
|
|
import datetime
|
|
import hashlib
|
|
|
|
# Configure logging
|
|
LOG_FOLDER = './logs'
|
|
os.makedirs(LOG_FOLDER, exist_ok=True)
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler(os.path.join(LOG_FOLDER, 'app.log')),
|
|
logging.StreamHandler()
|
|
]
|
|
)
|
|
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 = './static/app_config.json' # Moved to the static folder
|
|
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)
|
|
|
|
if not file_name or not file_url:
|
|
Logger.error(f"Invalid media entry: {media}")
|
|
continue
|
|
|
|
Logger.debug(f"Downloading file: {file_name} from {file_url}")
|
|
|
|
try:
|
|
response = requests.get(file_url, stream=True)
|
|
if response.status_code == 200:
|
|
with open(file_path, 'wb') as file:
|
|
for chunk in response.iter_content(chunk_size=8192):
|
|
file.write(chunk)
|
|
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}")
|
|
|
|
def calculate_file_hash(file_path):
|
|
"""Calculate the SHA256 hash of a file."""
|
|
if not os.path.exists(file_path):
|
|
return None
|
|
sha256 = hashlib.sha256()
|
|
with open(file_path, 'rb') as file:
|
|
while chunk := file.read(8192):
|
|
sha256.update(chunk)
|
|
return sha256.hexdigest()
|
|
|
|
# Download playlist files from server
|
|
def download_playlist_files_from_server():
|
|
Logger.info("Starting playlist file download using 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
|
|
|
|
server_ip = f'{server_address}:{port}'
|
|
url = f'http://{server_ip}/api/playlists'
|
|
params = {
|
|
'hostname': hostname,
|
|
'quickconnect_code': quickconnect_code
|
|
}
|
|
|
|
try:
|
|
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:
|
|
server_playlist = response.json().get('playlist', [])
|
|
if not server_playlist:
|
|
Logger.error("Playlist is empty or missing in the server response.")
|
|
return
|
|
|
|
Logger.info("Server playlist retrieved successfully.")
|
|
|
|
# Calculate the hash of the current local playlist
|
|
local_playlist_hash = calculate_file_hash(PLAYLIST_FILE)
|
|
|
|
# Calculate the hash of the server playlist
|
|
server_playlist_hash = hashlib.sha256(json.dumps(server_playlist, sort_keys=True).encode()).hexdigest()
|
|
|
|
# Compare hashes to determine if the playlist has changed
|
|
if local_playlist_hash == server_playlist_hash:
|
|
Logger.info("No changes detected in the server playlist. Skipping download.")
|
|
return
|
|
|
|
Logger.info("Changes detected in the server playlist. Downloading updated files...")
|
|
|
|
# Compare and download missing or updated files
|
|
for media in server_playlist:
|
|
file_name = media.get('file_name', '')
|
|
file_url = media.get('url', '')
|
|
file_path = os.path.join(RESOURCES_FOLDER, file_name)
|
|
|
|
if not file_name or not file_url:
|
|
Logger.error(f"Invalid media entry: {media}")
|
|
continue
|
|
|
|
# Check if the file exists locally and matches the server version
|
|
if not os.path.exists(file_path) or os.path.getsize(file_path) != media.get('size', 0):
|
|
Logger.info(f"Downloading file: {file_name}")
|
|
response = requests.get(file_url, stream=True)
|
|
if response.status_code == 200:
|
|
with open(file_path, 'wb') as file:
|
|
for chunk in response.iter_content(chunk_size=8192):
|
|
file.write(chunk)
|
|
Logger.info(f"Downloaded {file_name} to {file_path}")
|
|
else:
|
|
Logger.error(f"Failed to download {file_name}: {response.status_code}")
|
|
|
|
# Save the server playlist locally
|
|
save_playlist(server_playlist)
|
|
|
|
# Update the updated_playlist.json
|
|
create_updated_playlist()
|
|
|
|
except json.JSONDecodeError as e:
|
|
Logger.error(f"Failed to parse server playlist JSON: {e}")
|
|
else:
|
|
Logger.error(f"Failed to retrieve server 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 server playlist
|
|
if not os.path.exists(PLAYLIST_FILE):
|
|
Logger.error(f"Server 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() # Download playlist from the server
|
|
create_updated_playlist() # Create the updated playlist with local file paths
|
|
Logger.info("Playlist initialization complete.")
|
|
|
|
# Function to check for playlist updates every 5 minutes
|
|
def periodic_playlist_check():
|
|
while True:
|
|
try:
|
|
Logger.info("Checking for playlist updates...")
|
|
download_playlist_files_from_server() # Download playlist from the server
|
|
create_updated_playlist() # Create the updated playlist with local file paths
|
|
delete_old_logs_and_unused_files() # Delete old logs and unused media files
|
|
Logger.info("Playlist check complete.")
|
|
except Exception as e:
|
|
Logger.error(f"Error during playlist check: {e}")
|
|
time.sleep(300) # Wait for 5 minutes (300 seconds) before checking again
|
|
|
|
# Start the periodic playlist check in a background thread
|
|
def start_playlist_check_thread():
|
|
thread = threading.Thread(target=periodic_playlist_check, daemon=True)
|
|
thread.start()
|
|
|
|
# Function to delete log files older than 2 days
|
|
def delete_old_logs_and_unused_files(log_folder='./logs', resurse_folder='./static/resurse', days=2):
|
|
"""Delete old log files and unused media files."""
|
|
Logger.info("Checking for old log files to delete...")
|
|
if not os.path.exists(log_folder):
|
|
Logger.warning(f"Log folder does not exist: {log_folder}")
|
|
else:
|
|
now = time.time()
|
|
cutoff = now - (days * 86400) # Convert days to seconds
|
|
|
|
for file_name in os.listdir(log_folder):
|
|
file_path = os.path.join(log_folder, file_name)
|
|
if os.path.isfile(file_path):
|
|
file_modified_time = os.path.getmtime(file_path)
|
|
if file_modified_time < cutoff:
|
|
try:
|
|
os.remove(file_path)
|
|
Logger.info(f"Deleted old log file: {file_path}")
|
|
except Exception as e:
|
|
Logger.error(f"Failed to delete log file {file_path}: {e}")
|
|
|
|
Logger.info("Checking for unused media files to delete...")
|
|
if not os.path.exists(resurse_folder):
|
|
Logger.warning(f"Resurse folder does not exist: {resurse_folder}")
|
|
return
|
|
|
|
# Load the updated playlist to determine which files are in use
|
|
updated_playlist_file = './updated_playlist.json'
|
|
used_files = set()
|
|
if os.path.exists(updated_playlist_file):
|
|
with open(updated_playlist_file, 'r') as file:
|
|
updated_playlist = json.load(file)
|
|
for media in updated_playlist:
|
|
file_name = os.path.basename(media.get('url', ''))
|
|
used_files.add(file_name)
|
|
|
|
# Always keep playlist.json
|
|
used_files.add('playlist.json')
|
|
|
|
# Delete files in the resurse folder that are not in the updated playlist
|
|
for file_name in os.listdir(resurse_folder):
|
|
file_path = os.path.join(resurse_folder, file_name)
|
|
if os.path.isfile(file_path) and file_name not in used_files:
|
|
try:
|
|
os.remove(file_path)
|
|
Logger.info(f"Deleted unused media file: {file_path}")
|
|
except Exception as e:
|
|
Logger.error(f"Failed to delete media file {file_path}: {e}")
|
|
|
|
if __name__ == '__main__':
|
|
initialize_playlist() # Check and download playlist on startup
|
|
create_updated_playlist() # Create the updated playlist
|
|
start_playlist_check_thread() # Start the background thread for periodic checks
|
|
app.run(host='0.0.0.0', port=1025)
|
|
|