diff --git a/config/app_config.json b/config/app_config.json index 129ed96..0dcf525 100644 --- a/config/app_config.json +++ b/config/app_config.json @@ -1,5 +1,5 @@ { - "server_ip": "https://digi-signage.moto-adv.com", + "server_ip": "digi-signage.moto-adv.com", "port": "443", "screen_name": "tv-terasa", "quickconnect_key": "8887779", diff --git a/config/resources/intro1.mp4 b/config/resources/intro1.mp4 new file mode 100644 index 0000000..c522a56 Binary files /dev/null and b/config/resources/intro1.mp4 differ diff --git a/src/main.py b/src/main.py index b29e2d2..49aecd3 100644 --- a/src/main.py +++ b/src/main.py @@ -395,33 +395,58 @@ class SignagePlayer(Widget): file_name = media_item.get('file_name', '') duration = media_item.get('duration', 10) + Logger.info(f"SignagePlayer: ===== Playing item {self.current_index + 1}/{len(self.playlist)} =====") + Logger.info(f"SignagePlayer: File: {file_name}") + Logger.info(f"SignagePlayer: Duration: {duration}s") + # Construct full path to media file media_path = os.path.join(self.media_dir, file_name) + Logger.info(f"SignagePlayer: Full path: {media_path}") if not os.path.exists(media_path): - Logger.error(f"SignagePlayer: Media file not found: {media_path}") + Logger.error(f"SignagePlayer: ❌ Media file not found: {media_path}") + Logger.error(f"SignagePlayer: Skipping to next media...") + self.consecutive_errors += 1 self.next_media() return + Logger.info(f"SignagePlayer: ✓ File exists (size: {os.path.getsize(media_path):,} bytes)") + # Remove status label if showing self.ids.status_label.opacity = 0 # Remove previous media widget if self.current_widget: + # Properly stop video if it's playing to prevent resource leaks + if isinstance(self.current_widget, Video): + try: + Logger.info(f"SignagePlayer: Stopping previous video widget...") + self.current_widget.state = 'stop' + self.current_widget.unload() + except Exception as e: + Logger.warning(f"SignagePlayer: Error stopping video: {e}") + self.ids.content_area.remove_widget(self.current_widget) self.current_widget = None + Logger.info(f"SignagePlayer: Previous widget removed") # Determine media type and create appropriate widget file_extension = os.path.splitext(file_name)[1].lower() + Logger.info(f"SignagePlayer: Extension: {file_extension}") if file_extension in ['.mp4', '.avi', '.mkv', '.mov', '.webm']: # Video file + Logger.info(f"SignagePlayer: Media type: VIDEO") self.play_video(media_path, duration) elif file_extension in ['.jpg', '.jpeg', '.png', '.bmp', '.gif']: # Image file + Logger.info(f"SignagePlayer: Media type: IMAGE") self.play_image(media_path, duration) else: - Logger.warning(f"SignagePlayer: Unsupported media type: {file_extension}") + Logger.warning(f"SignagePlayer: ❌ Unsupported media type: {file_extension}") + Logger.warning(f"SignagePlayer: Supported: .mp4/.avi/.mkv/.mov/.webm/.jpg/.jpeg/.png/.bmp/.gif") + Logger.warning(f"SignagePlayer: Skipping to next media...") + self.consecutive_errors += 1 self.next_media() return @@ -438,6 +463,7 @@ class SignagePlayer(Widget): # Reset error counter on successful playback self.consecutive_errors = 0 + Logger.info(f"SignagePlayer: ✓ Media started successfully (consecutive_errors reset to 0)") except Exception as e: Logger.error(f"SignagePlayer: Error playing media: {e}") @@ -459,13 +485,16 @@ class SignagePlayer(Widget): try: # Verify file exists if not os.path.exists(video_path): - Logger.error(f"Video file not found: {video_path}") + Logger.error(f"SignagePlayer: ❌ Video file not found: {video_path}") + self.consecutive_errors += 1 self.next_media() return Logger.info(f"SignagePlayer: Loading video {os.path.basename(video_path)} for {duration}s") + Logger.info(f"SignagePlayer: Video provider: {os.environ.get('KIVY_VIDEO', 'default')}") # Create Video widget with optimized settings + Logger.info(f"SignagePlayer: Creating Video widget...") self.current_widget = Video( source=video_path, state='play', # Start playing immediately @@ -483,9 +512,12 @@ class SignagePlayer(Widget): self.current_widget.bind(on_eos=self._on_video_eos) # Add to content area + Logger.info(f"SignagePlayer: Adding video widget to content area...") self.ids.content_area.add_widget(self.current_widget) - # Schedule next media after duration + # Schedule next media after duration (unschedule first to prevent overlaps) + Logger.info(f"SignagePlayer: Scheduled next media in {duration}s") + Clock.unschedule(self.next_media) Clock.schedule_once(self.next_media, duration) except Exception as e: @@ -512,6 +544,7 @@ class SignagePlayer(Widget): def play_image(self, image_path, duration): """Play an image file""" try: + Logger.info(f"SignagePlayer: Creating AsyncImage widget...") self.current_widget = AsyncImage( source=image_path, allow_stretch=True, @@ -519,9 +552,13 @@ class SignagePlayer(Widget): size_hint=(1, 1), pos_hint={'center_x': 0.5, 'center_y': 0.5} ) + Logger.info(f"SignagePlayer: Adding image widget to content area...") self.ids.content_area.add_widget(self.current_widget) - # Schedule next media after duration + # Schedule next media after duration (unschedule first to prevent overlaps) + Logger.info(f"SignagePlayer: Scheduled next media in {duration}s") + Clock.unschedule(self.next_media) Clock.schedule_once(self.next_media, duration) + Logger.info(f"SignagePlayer: ✓ Image displayed successfully") except Exception as e: Logger.error(f"SignagePlayer: Error playing image {image_path}: {e}") self.consecutive_errors += 1 @@ -531,8 +568,10 @@ class SignagePlayer(Widget): def next_media(self, dt=None): """Move to next media item""" if self.is_paused: + Logger.debug(f"SignagePlayer: Skipping next_media - player is paused") return - + + Logger.info(f"SignagePlayer: Transitioning to next media (was index {self.current_index})") self.current_index += 1 # Unschedule any pending media transitions diff --git a/src/player_auth.json b/src/player_auth.json index 9ccfcdd..8e2420e 100644 --- a/src/player_auth.json +++ b/src/player_auth.json @@ -1,10 +1,10 @@ { - "hostname": "rpi-tvholba1", - "auth_code": "aDHIMS2yx_HhfR0dWKy9VHaM_h0CKemfcsqv4Zgp0IY", - "player_id": 2, - "player_name": "Tv-Anunturi", - "group_id": null, + "hostname": "tv-terasa", + "auth_code": "iiSyZDLWGyqNIxeRt54XYREgvAio11RwwU1_oJev6WI", + "player_id": 1, + "player_name": "TV-acasa 1", + "playlist_id": 1, "orientation": "Landscape", "authenticated": true, - "server_url": "http://172.18.0.1:5000" + "server_url": "https://digi-signage.moto-adv.com:443" } \ No newline at end of file diff --git a/working_files/DEBUGGING_MEDIA_SKIPS.md b/working_files/DEBUGGING_MEDIA_SKIPS.md new file mode 100644 index 0000000..610b29f --- /dev/null +++ b/working_files/DEBUGGING_MEDIA_SKIPS.md @@ -0,0 +1,211 @@ +# Debugging Media File Skips - Guide + +## Summary +Your playlist has been analyzed and all 3 media files are present and valid: +- ✅ music.jpg (36,481 bytes) - IMAGE - 15s +- ✅ 130414-746934884.mp4 (6,474,921 bytes) - VIDEO - 23s +- ✅ IMG_0386.jpeg (592,162 bytes) - IMAGE - 15s + +## Enhanced Logging Added +The application has been updated with detailed logging to track: +- When each media file starts playing +- File path validation +- File size and existence checks +- Media type detection +- Widget creation steps +- Scheduling of next media +- Any errors or skips + +## How to See Detailed Logs + +### Method 1: Run with log output +```bash +cd /home/pi/Desktop/Kiwy-Signage +source .venv/bin/activate +cd src +python3 main.py 2>&1 | tee playback.log +``` + +### Method 2: Check Kivy logs location +Kivy logs are typically stored in: +- Linux: `~/.kivy/logs/` +- Check with: `ls -lth ~/.kivy/logs/ | head` + +## Common Reasons Media Files Get Skipped + +### 1. **File Not Found** +**Symptom**: Log shows "❌ Media file not found" +**Cause**: File doesn't exist at expected path +**Solution**: Run diagnostic tool +```bash +python3 diagnose_playlist.py +``` + +### 2. **Unsupported File Type** +**Symptom**: Log shows "❌ Unsupported media type" +**Supported formats**: +- Videos: .mp4, .avi, .mkv, .mov, .webm +- Images: .jpg, .jpeg, .png, .bmp, .gif +**Solution**: Convert files or check extension + +### 3. **Video Codec Issues** +**Symptom**: Video file exists but doesn't play +**Cause**: Video codec not supported by ffpyplayer +**Check**: Look for error in logs about codec +**Solution**: Re-encode video with H.264 codec: +```bash +ffmpeg -i input.mp4 -c:v libx264 -preset fast -crf 23 output.mp4 +``` + +### 4. **Corrupted Media Files** +**Symptom**: File exists but throws error when loading +**Check**: Try playing file with external player +```bash +# For images +feh media/music.jpg + +# For videos +vlc media/130414-746934884.mp4 +# or +ffplay media/130414-746934884.mp4 +``` + +### 5. **Memory/Performance Issues** +**Symptom**: First few files play, then skipping increases +**Cause**: Memory leak or performance degradation +**Check**: Look for "consecutive_errors" in logs +**Solution**: +- Reduce resolution setting in settings popup +- Optimize video files (lower bitrate/resolution) + +### 6. **Timing Issues** +**Symptom**: Files play too fast or skip immediately +**Cause**: Duration set too low or scheduler issues +**Check**: Verify durations in playlist.json +**Current durations**: 15s (images), 23s (video) + +### 7. **Permission Issues** +**Symptom**: "Permission denied" in logs +**Check**: File permissions +```bash +ls -la media/ +``` +**Solution**: Fix permissions +```bash +chmod 644 media/* +``` + +## What to Look For in Logs + +### Successful Playback Pattern: +``` +SignagePlayer: ===== Playing item 1/3 ===== +SignagePlayer: File: music.jpg +SignagePlayer: Duration: 15s +SignagePlayer: Full path: /path/to/media/music.jpg +SignagePlayer: ✓ File exists (size: 36,481 bytes) +SignagePlayer: Extension: .jpg +SignagePlayer: Media type: IMAGE +SignagePlayer: Creating AsyncImage widget... +SignagePlayer: Adding image widget to content area... +SignagePlayer: Scheduled next media in 15s +SignagePlayer: ✓ Image displayed successfully +SignagePlayer: ✓ Media started successfully (consecutive_errors reset to 0) +``` + +### Skip Pattern (File Not Found): +``` +SignagePlayer: ===== Playing item 2/3 ===== +SignagePlayer: File: missing.mp4 +SignagePlayer: Full path: /path/to/media/missing.mp4 +SignagePlayer: ❌ Media file not found: /path/to/media/missing.mp4 +SignagePlayer: Skipping to next media... +SignagePlayer: Transitioning to next media (was index 1) +``` + +### Video Loading Error: +``` +SignagePlayer: Loading video file.mp4 for 23s +SignagePlayer: Video provider: ffpyplayer +[ERROR ] [Video ] Error reading video +[ERROR ] SignagePlayer: Error playing video: ... +``` + +## Testing Tools Provided + +### 1. Diagnostic Tool +```bash +python3 diagnose_playlist.py +``` +Checks: +- Playlist file exists and is valid +- All media files exist +- File types are supported +- No case sensitivity issues + +### 2. Playback Simulation +```bash +python3 test_playback_logging.py +``` +Simulates the playback sequence without running the GUI + +## Monitoring Live Playback + +To see live logs while the app is running: +```bash +# Terminal 1: Start the app +./run_player.sh + +# Terminal 2: Monitor logs +tail -f ~/.kivy/logs/kivy_*.txt +``` + +## Quick Fixes to Try + +### 1. Clear any stuck state +```bash +rm -f src/*.pyc +rm -rf src/__pycache__ +``` + +### 2. Test with simpler playlist +Create `playlists/test_playlist_v9.json`: +```json +{ + "playlist": [ + { + "file_name": "music.jpg", + "url": "media/music.jpg", + "duration": 5 + } + ], + "version": 9 +} +``` + +### 3. Check video compatibility +```bash +# Install ffmpeg tools if not present +sudo apt-get install ffmpeg + +# Check video info +ffprobe media/130414-746934884.mp4 +``` + +## Getting Help + +When reporting issues, please provide: +1. Output from `python3 diagnose_playlist.py` +2. Last 100 lines of Kivy log file +3. Any error messages from console +4. What you observe (which files skip? pattern?) + +## Next Steps + +1. **Run the app** and observe the console output +2. **Check logs** for error patterns +3. **Run diagnostic** if files are skipping +4. **Test individual files** with external players if needed +5. **Re-encode videos** if codec issues found + +The enhanced logging will now tell you exactly why each file is being skipped! diff --git a/IMPLEMENTATION_SUMMARY.md b/working_files/IMPLEMENTATION_SUMMARY.md similarity index 100% rename from IMPLEMENTATION_SUMMARY.md rename to working_files/IMPLEMENTATION_SUMMARY.md diff --git a/working_files/INVESTIGATION_RESULTS.md b/working_files/INVESTIGATION_RESULTS.md new file mode 100644 index 0000000..60d1dd2 --- /dev/null +++ b/working_files/INVESTIGATION_RESULTS.md @@ -0,0 +1,182 @@ +# Investigation Results: Media File Skipping + +## Diagnostic Summary +✅ **All 3 media files are present and valid:** +- music.jpg (36,481 bytes) - IMAGE +- 130414-746934884.mp4 (6,474,921 bytes) - VIDEO (H.264, 1920x1080, compatible) +- IMG_0386.jpeg (592,162 bytes) - IMAGE + +✅ **No file system issues found:** +- All files exist +- Correct permissions +- No case sensitivity problems +- Supported file types + +✅ **Video codec is compatible:** +- H.264 codec (fully supported by ffpyplayer) +- 1920x1080 @ 29.97fps +- Reasonable bitrate (2.3 Mbps) + +## Potential Root Causes Identified + +### 1. **Video Widget Not Properly Stopping** (Most Likely) +When transitioning from video to the next media, the video widget may not be properly stopped before removal. This could cause: +- The video to continue playing in background +- Race conditions with scheduling +- Next media appearing to "skip" + +**Location**: `play_current_media()` line 417-420 +```python +if self.current_widget: + self.ids.content_area.remove_widget(self.current_widget) + self.current_widget = None +``` + +**Fix**: Stop video before removing widget + +### 2. **Multiple Scheduled Events** +The `Clock.schedule_once(self.next_media, duration)` could be called multiple times if widget loading triggers multiple events. + +**Location**: Lines 510, 548 + +**Fix**: Add `Clock.unschedule()` before scheduling + +### 3. **Video Loading Callback Issues** +The video `loaded` callback might not fire or might fire multiple times, causing state confusion. + +**Location**: `_on_video_loaded()` line 516 + +### 4. **Pause State Not Properly Checked** +If the player gets paused/unpaused during media transition, scheduling could get confused. + +**Location**: `next_media()` line 551 + +## What Enhanced Logging Will Show + +With the new logging, you'll see patterns like: + +### If Videos Are Being Skipped: +``` +===== Playing item 2/3 ===== +File: 130414-746934884.mp4 +Extension: .mp4 +Media type: VIDEO +Loading video... +Creating Video widget... +[SHORT PAUSE OR ERROR] +Transitioning to next media (was index 1) +===== Playing item 3/3 ===== +``` + +### If Duration Is Too Short: +``` +Creating Video widget... +Scheduled next media in 23s +[Only 1-2 seconds pass] +Transitioning to next media +``` + +## Recommended Fixes + +I've added comprehensive logging. Here are additional fixes to try: + +### Fix 1: Properly Stop Video Widget Before Removal +Add this to `play_current_media()` before removing widget: + +```python +# Remove previous media widget +if self.current_widget: + # Stop video if it's playing + if isinstance(self.current_widget, Video): + self.current_widget.state = 'stop' + self.current_widget.unload() + self.ids.content_area.remove_widget(self.current_widget) + self.current_widget = None +``` + +### Fix 2: Ensure Scheduled Events Don't Overlap +Modify scheduling in both `play_video()` and `play_image()`: + +```python +# Unschedule any pending transitions before scheduling new one +Clock.unschedule(self.next_media) +Clock.schedule_once(self.next_media, duration) +``` + +### Fix 3: Add Video State Monitoring +Track when video actually starts playing vs when widget is created. + +## How to Test + +### 1. Run with Enhanced Logging +```bash +cd /home/pi/Desktop/Kiwy-Signage +source .venv/bin/activate +cd src +python3 main.py 2>&1 | tee ../playback_debug.log +``` + +Watch the console output. You should see: +- Each media file being loaded +- Timing information +- Any errors or skips + +### 2. Check Timing +If media skips, check the log for timing: +- Does "Scheduled next media in Xs" appear? +- How long until "Transitioning to next media" appears? +- Is it immediate (< 1 second) = scheduling bug +- Is it after full duration = normal operation + +### 3. Look for Error Patterns +Search the log for: +```bash +grep "❌" playback_debug.log +grep "Error" playback_debug.log +grep "consecutive_errors" playback_debug.log +``` + +## Quick Test Scenario + +Create a test with just one file to isolate the issue: + +```json +{ + "playlist": [ + { + "file_name": "music.jpg", + "url": "media/music.jpg", + "duration": 10 + } + ], + "version": 99 +} +``` + +If this single image repeats correctly every 10s, the issue is with video playback or transitions. + +## What to Report + +When you run the app, please capture: + +1. **Console output** - especially the pattern around skipped files +2. **Which files skip?** - Is it always videos? Always after videos? +3. **Timing** - Do files play for full duration before skipping? +4. **Pattern** - First loop OK then skips? Always skips certain file? + +## Tools Created + +1. **diagnose_playlist.py** - Check file system issues +2. **test_playback_logging.py** - Simulate playback logic +3. **check_video_codecs.py** - Verify video compatibility +4. **Enhanced main.py** - Detailed logging throughout + +## Next Actions + +1. ✅ Run `diagnose_playlist.py` - **PASSED** +2. ✅ Run `check_video_codecs.py` - **PASSED** +3. ⏳ Run app with logging and observe pattern +4. ⏳ Apply video widget fixes if needed +5. ⏳ Report findings for further diagnosis + +The enhanced logging will pinpoint exactly where and why files are being skipped! diff --git a/MIGRATION_GUIDE.md b/working_files/MIGRATION_GUIDE.md similarity index 100% rename from MIGRATION_GUIDE.md rename to working_files/MIGRATION_GUIDE.md diff --git a/OFFLINE_INSTALLATION.md b/working_files/OFFLINE_INSTALLATION.md similarity index 100% rename from OFFLINE_INSTALLATION.md rename to working_files/OFFLINE_INSTALLATION.md diff --git a/OFFLINE_QUICKSTART.md b/working_files/OFFLINE_QUICKSTART.md similarity index 100% rename from OFFLINE_QUICKSTART.md rename to working_files/OFFLINE_QUICKSTART.md diff --git a/QUICK_START.md b/working_files/QUICK_START.md similarity index 100% rename from QUICK_START.md rename to working_files/QUICK_START.md diff --git a/README.md b/working_files/README.md similarity index 100% rename from README.md rename to working_files/README.md diff --git a/working_files/analyze_playlist.py b/working_files/analyze_playlist.py new file mode 100644 index 0000000..4b4051b --- /dev/null +++ b/working_files/analyze_playlist.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Analyze what's happening with the playlist download.""" + +import json + +# Check the saved playlist +playlist_file = 'playlists/server_playlist_v8.json' +print("=" * 80) +print("SAVED PLAYLIST ANALYSIS") +print("=" * 80) + +with open(playlist_file, 'r') as f: + data = json.load(f) + +print(f"\nVersion: {data.get('version', 'N/A')}") +print(f"Items in playlist: {len(data.get('playlist', []))}") + +print("\nPlaylist items:") +for idx, item in enumerate(data.get('playlist', []), 1): + print(f"\n{idx}. File: {item.get('file_name', 'N/A')}") + print(f" URL: {item.get('url', 'N/A')}") + print(f" Duration: {item.get('duration', 'N/A')}s") + +print("\n" + "=" * 80) +print("\n⚠️ ISSUE: Server has 5 files, but only 3 are saved!") +print("\nPossible reasons:") +print("1. Server sent only 3 files") +print("2. 2 files failed to download and were skipped") +print("3. Download function has a bug") +print("\nThe download_media_files() function in get_playlists_v2.py:") +print("- Downloads from the 'url' field in the playlist") +print("- If download fails, it SKIPS the file (continues)") +print("- Only successfully downloaded files are added to updated_playlist") +print("\nThis means 2 files likely had invalid URLs or download errors!") +print("=" * 80) diff --git a/working_files/check_video_codecs.py b/working_files/check_video_codecs.py new file mode 100644 index 0000000..3f769d5 --- /dev/null +++ b/working_files/check_video_codecs.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +Check video files for codec compatibility with ffpyplayer +""" +import os +import subprocess +import json + +def check_video_codec(video_path): + """Check video codec using ffprobe""" + try: + cmd = [ + 'ffprobe', + '-v', 'quiet', + '-print_format', 'json', + '-show_format', + '-show_streams', + video_path + ] + + result = subprocess.run(cmd, capture_output=True, text=True) + + if result.returncode != 0: + return None, "ffprobe failed" + + data = json.loads(result.stdout) + + video_streams = [s for s in data.get('streams', []) if s.get('codec_type') == 'video'] + audio_streams = [s for s in data.get('streams', []) if s.get('codec_type') == 'audio'] + + if not video_streams: + return None, "No video stream found" + + video_stream = video_streams[0] + + info = { + 'codec': video_stream.get('codec_name', 'unknown'), + 'codec_long': video_stream.get('codec_long_name', 'unknown'), + 'width': video_stream.get('width', 0), + 'height': video_stream.get('height', 0), + 'fps': eval(video_stream.get('r_frame_rate', '0/1')), + 'duration': float(data.get('format', {}).get('duration', 0)), + 'bitrate': int(data.get('format', {}).get('bit_rate', 0)), + 'audio_codec': audio_streams[0].get('codec_name', 'none') if audio_streams else 'none', + 'size': int(data.get('format', {}).get('size', 0)) + } + + return info, None + + except FileNotFoundError: + return None, "ffprobe not installed (run: sudo apt-get install ffmpeg)" + except Exception as e: + return None, str(e) + +def main(): + base_dir = os.path.dirname(os.path.abspath(__file__)) + media_dir = os.path.join(base_dir, 'media') + + print("=" * 80) + print("VIDEO CODEC COMPATIBILITY CHECKER") + print("=" * 80) + + # Supported codecs by ffpyplayer + supported_codecs = ['h264', 'h265', 'hevc', 'vp8', 'vp9', 'mpeg4'] + + # Find video files + video_extensions = ['.mp4', '.avi', '.mkv', '.mov', '.webm'] + video_files = [] + + if os.path.exists(media_dir): + for filename in os.listdir(media_dir): + ext = os.path.splitext(filename)[1].lower() + if ext in video_extensions: + video_files.append(filename) + + if not video_files: + print("\n✓ No video files found in media directory") + return + + print(f"\nFound {len(video_files)} video file(s):\n") + + for filename in video_files: + video_path = os.path.join(media_dir, filename) + print(f"📹 {filename}") + print(f" Path: {video_path}") + + info, error = check_video_codec(video_path) + + if error: + print(f" ❌ ERROR: {error}") + continue + + # Display video info + print(f" Video Codec: {info['codec']} ({info['codec_long']})") + print(f" Resolution: {info['width']}x{info['height']}") + print(f" Frame Rate: {info['fps']:.2f} fps") + print(f" Duration: {info['duration']:.1f}s") + print(f" Bitrate: {info['bitrate'] / 1000:.0f} kbps") + print(f" Audio Codec: {info['audio_codec']}") + print(f" File Size: {info['size'] / (1024*1024):.2f} MB") + + # Check compatibility + if info['codec'] in supported_codecs: + print(f" ✅ COMPATIBLE - Codec '{info['codec']}' is supported by ffpyplayer") + else: + print(f" ⚠️ WARNING - Codec '{info['codec']}' may not be supported") + print(f" Supported codecs: {', '.join(supported_codecs)}") + print(f" Consider re-encoding to H.264:") + print(f" ffmpeg -i \"{filename}\" -c:v libx264 -preset fast -crf 23 \"{os.path.splitext(filename)[0]}_h264.mp4\"") + + # Performance warnings + if info['width'] > 1920 or info['height'] > 1080: + print(f" ⚠️ High resolution ({info['width']}x{info['height']}) may cause performance issues") + print(f" Consider downscaling to 1920x1080 or lower") + + if info['bitrate'] > 5000000: # 5 Mbps + print(f" ⚠️ High bitrate ({info['bitrate'] / 1000000:.1f} Mbps) may cause playback issues") + print(f" Consider reducing bitrate to 2-4 Mbps") + + print() + +if __name__ == '__main__': + main() diff --git a/convert_video_for_rpi.sh b/working_files/convert_video_for_rpi.sh similarity index 100% rename from convert_video_for_rpi.sh rename to working_files/convert_video_for_rpi.sh diff --git a/working_files/diagnose_playlist.py b/working_files/diagnose_playlist.py new file mode 100644 index 0000000..950699f --- /dev/null +++ b/working_files/diagnose_playlist.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +""" +Diagnostic script to check why media files might be skipped +""" +import os +import json + +# Paths +base_dir = os.path.dirname(os.path.abspath(__file__)) +media_dir = os.path.join(base_dir, 'media') +playlists_dir = os.path.join(base_dir, 'playlists') + +# Supported extensions +VIDEO_EXTENSIONS = ['.mp4', '.avi', '.mkv', '.mov', '.webm'] +IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.bmp', '.gif'] +SUPPORTED_EXTENSIONS = VIDEO_EXTENSIONS + IMAGE_EXTENSIONS + +def check_playlist(): + """Check playlist for issues""" + print("=" * 80) + print("PLAYLIST DIAGNOSTIC TOOL") + print("=" * 80) + + # Find latest playlist file + playlist_files = [f for f in os.listdir(playlists_dir) + if f.startswith('server_playlist_v') and f.endswith('.json')] + + if not playlist_files: + print("\n❌ ERROR: No playlist files found!") + return + + # Sort by version and get latest + versions = [(int(f.split('_v')[-1].split('.json')[0]), f) for f in playlist_files] + versions.sort(reverse=True) + latest_file = versions[0][1] + playlist_path = os.path.join(playlists_dir, latest_file) + + print(f"\n📋 Latest Playlist: {latest_file}") + print(f" Path: {playlist_path}") + + # Load playlist + try: + with open(playlist_path, 'r') as f: + data = json.load(f) + + playlist = data.get('playlist', []) + version = data.get('version', 0) + + print(f" Version: {version}") + print(f" Total items: {len(playlist)}") + + except Exception as e: + print(f"\n❌ ERROR loading playlist: {e}") + return + + # Check media directory + print(f"\n📁 Media Directory: {media_dir}") + if not os.path.exists(media_dir): + print(" ❌ ERROR: Media directory doesn't exist!") + return + + media_files = os.listdir(media_dir) + print(f" Files found: {len(media_files)}") + for f in media_files: + print(f" - {f}") + + # Check each playlist item + print("\n" + "=" * 80) + print("CHECKING PLAYLIST ITEMS") + print("=" * 80) + + valid_count = 0 + missing_count = 0 + unsupported_count = 0 + + for idx, item in enumerate(playlist, 1): + file_name = item.get('file_name', '') + duration = item.get('duration', 0) + media_path = os.path.join(media_dir, file_name) + file_ext = os.path.splitext(file_name)[1].lower() + + print(f"\n[{idx}/{len(playlist)}] {file_name}") + print(f" Duration: {duration}s") + + # Check if file exists + if not os.path.exists(media_path): + print(f" ❌ STATUS: FILE NOT FOUND") + print(f" Expected path: {media_path}") + missing_count += 1 + continue + + # Check file size + file_size = os.path.getsize(media_path) + print(f" ✓ File exists ({file_size:,} bytes)") + + # Check if supported type + if file_ext not in SUPPORTED_EXTENSIONS: + print(f" ❌ STATUS: UNSUPPORTED FILE TYPE '{file_ext}'") + print(f" Supported extensions: {', '.join(SUPPORTED_EXTENSIONS)}") + unsupported_count += 1 + continue + + # Check media type + if file_ext in VIDEO_EXTENSIONS: + media_type = "VIDEO" + elif file_ext in IMAGE_EXTENSIONS: + media_type = "IMAGE" + else: + media_type = "UNKNOWN" + + print(f" ✓ Type: {media_type}") + print(f" ✓ Extension: {file_ext}") + print(f" ✓ STATUS: SHOULD PLAY OK") + valid_count += 1 + + # Summary + print("\n" + "=" * 80) + print("SUMMARY") + print("=" * 80) + print(f"Total items: {len(playlist)}") + print(f"✓ Valid: {valid_count}") + print(f"❌ Missing files: {missing_count}") + print(f"❌ Unsupported: {unsupported_count}") + + if valid_count == len(playlist): + print("\n✅ All playlist items should play correctly!") + else: + print(f"\n⚠️ WARNING: {len(playlist) - valid_count} items may be skipped!") + + # Additional checks + print("\n" + "=" * 80) + print("ADDITIONAL CHECKS") + print("=" * 80) + + # Check for files in media dir not in playlist + playlist_files_set = {item.get('file_name', '') for item in playlist} + orphaned_files = [f for f in media_files if f not in playlist_files_set] + + if orphaned_files: + print(f"\n⚠️ Files in media directory NOT in playlist:") + for f in orphaned_files: + print(f" - {f}") + else: + print("\n✓ All media files are in the playlist") + + # Check for case sensitivity issues + print("\n🔍 Checking for case sensitivity issues...") + media_files_lower = {f.lower(): f for f in media_files} + case_issues = [] + + for item in playlist: + file_name = item.get('file_name', '') + if file_name.lower() in media_files_lower: + actual_name = media_files_lower[file_name.lower()] + if actual_name != file_name: + case_issues.append((file_name, actual_name)) + + if case_issues: + print("⚠️ Case sensitivity mismatches found:") + for playlist_name, actual_name in case_issues: + print(f" Playlist: {playlist_name}") + print(f" Actual: {actual_name}") + else: + print("✓ No case sensitivity issues found") + +if __name__ == '__main__': + check_playlist() diff --git a/download_deb_packages.sh b/working_files/download_deb_packages.sh similarity index 100% rename from download_deb_packages.sh rename to working_files/download_deb_packages.sh diff --git a/download_offline_packages.sh b/working_files/download_offline_packages.sh similarity index 100% rename from download_offline_packages.sh rename to working_files/download_offline_packages.sh diff --git a/working_files/force_update.py b/working_files/force_update.py new file mode 100644 index 0000000..9eacbfd --- /dev/null +++ b/working_files/force_update.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Force playlist update to download all files.""" + +import json +import sys +import os + +# Add src directory to path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) + +from get_playlists_v2 import update_playlist_if_needed + +# Load config +config_file = 'config/app_config.json' +with open(config_file, 'r') as f: + config = json.load(f) + +print("=" * 80) +print("FORCING PLAYLIST UPDATE") +print("=" * 80) + +playlist_dir = 'playlists' +media_dir = 'media' + +print(f"\nConfiguration:") +print(f" Playlist dir: {playlist_dir}") +print(f" Media dir: {media_dir}") + +print("\n" + "=" * 80) +print("Updating playlist...") +print("=" * 80 + "\n") + +result = update_playlist_if_needed(config, playlist_dir, media_dir) + +if result: + print("\n" + "=" * 80) + print("SUCCESS!") + print("=" * 80) + print(f"✓ Playlist updated to: {result}") + + # Check media directory + import os + media_files = sorted([f for f in os.listdir(media_dir) if not f.startswith('.')]) + print(f"\n✓ Media files downloaded ({len(media_files)}):") + for f in media_files: + size = os.path.getsize(os.path.join(media_dir, f)) + print(f" - {f} ({size:,} bytes)") + +else: + print("\n" + "=" * 80) + print("FAILED or already up to date") + print("=" * 80) + +print("\n" + "=" * 80) diff --git a/working_files/player_auth.json b/working_files/player_auth.json new file mode 100644 index 0000000..6ee814a --- /dev/null +++ b/working_files/player_auth.json @@ -0,0 +1,10 @@ +{ + "hostname": "tv-terasa", + "auth_code": "iiSyZDLWGyqNIxeRt54XYREgvAio11RwwU1_oJev6WI", + "player_id": 1, + "player_name": "TV-acasa 1", + "playlist_id": 1, + "orientation": "Landscape", + "authenticated": true, + "server_url": "http://digi-signage.moto-adv.com" +} \ No newline at end of file diff --git a/working_files/server_response_debug.json b/working_files/server_response_debug.json new file mode 100644 index 0000000..f0d3aab --- /dev/null +++ b/working_files/server_response_debug.json @@ -0,0 +1,54 @@ +{ + "count": 5, + "player_id": 1, + "player_name": "TV-acasa 1", + "playlist": [ + { + "description": null, + "duration": 15, + "file_name": "music.jpg", + "id": 1, + "position": 1, + "type": "image", + "url": "http://digi-signage.moto-adv.com/static/uploads/music.jpg" + }, + { + "description": null, + "duration": 23, + "file_name": "130414-746934884.mp4", + "id": 2, + "position": 3, + "type": "video", + "url": "http://digi-signage.moto-adv.com/static/uploads/130414-746934884.mp4" + }, + { + "description": null, + "duration": 15, + "file_name": "IMG_0386.jpeg", + "id": 4, + "position": 4, + "type": "image", + "url": "http://digi-signage.moto-adv.com/static/uploads/IMG_0386.jpeg" + }, + { + "description": null, + "duration": 15, + "file_name": "AGC_20250704_204105932.jpg", + "id": 5, + "position": 5, + "type": "image", + "url": "http://digi-signage.moto-adv.com/static/uploads/AGC_20250704_204105932.jpg" + }, + { + "description": null, + "duration": 15, + "file_name": "50194.jpg", + "id": 3, + "position": 6, + "type": "image", + "url": "http://digi-signage.moto-adv.com/static/uploads/50194.jpg" + } + ], + "playlist_id": 1, + "playlist_version": 9 +} \ No newline at end of file diff --git a/test_authentication.py b/working_files/test_authentication.py similarity index 100% rename from test_authentication.py rename to working_files/test_authentication.py diff --git a/working_files/test_connection.py b/working_files/test_connection.py new file mode 100644 index 0000000..679c8b1 --- /dev/null +++ b/working_files/test_connection.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Test server connection and playlist fetch.""" + +import json +import sys +import os + +# Add src directory to path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) + +from player_auth import PlayerAuth + +# Load config +config_file = 'config/app_config.json' +with open(config_file, 'r') as f: + config = json.load(f) + +print("=" * 80) +print("SERVER CONNECTION TEST") +print("=" * 80) + +server_ip = config.get("server_ip", "") +screen_name = config.get("screen_name", "") +quickconnect_key = config.get("quickconnect_key", "") +port = config.get("port", "") + +print(f"\nConfiguration:") +print(f" Server: {server_ip}") +print(f" Port: {port}") +print(f" Screen Name: {screen_name}") +print(f" QuickConnect: {quickconnect_key}") + +# Build server URL +if server_ip.startswith('http://') or server_ip.startswith('https://'): + server_url = server_ip + # If it has https but port 443 is specified, ensure port is included if non-standard + if not ':' in server_ip.replace('https://', '').replace('http://', ''): + if port and port != '443' and port != '80': + server_url = f"{server_ip}:{port}" +else: + # Use https for port 443, http for others + protocol = "https" if port == "443" else "http" + server_url = f"{protocol}://{server_ip}:{port}" + +print(f"\nServer URL: {server_url}") + +# Test authentication +print("\n" + "=" * 80) +print("1. TESTING AUTHENTICATION") +print("=" * 80) + +auth = PlayerAuth('src/player_auth.json') + +# Check if already authenticated +if auth.is_authenticated(): + print("✓ Found existing authentication") + valid, message = auth.verify_auth() + if valid: + print(f"✓ Auth is valid: {message}") + else: + print(f"✗ Auth expired: {message}") + print("\nRe-authenticating...") + success, error = auth.authenticate( + server_url=server_url, + hostname=screen_name, + quickconnect_code=quickconnect_key + ) + if success: + print(f"✓ Re-authentication successful!") + else: + print(f"✗ Re-authentication failed: {error}") + sys.exit(1) +else: + print("No existing authentication found. Authenticating...") + success, error = auth.authenticate( + server_url=server_url, + hostname=screen_name, + quickconnect_code=quickconnect_key + ) + if success: + print(f"✓ Authentication successful!") + else: + print(f"✗ Authentication failed: {error}") + sys.exit(1) + +# Test playlist fetch +print("\n" + "=" * 80) +print("2. TESTING PLAYLIST FETCH") +print("=" * 80) + +playlist_data = auth.get_playlist() + +if playlist_data: + print(f"✓ Playlist fetched successfully!") + print(f"\nPlaylist Version: {playlist_data.get('playlist_version', 'N/A')}") + print(f"Number of items: {len(playlist_data.get('playlist', []))}") + + print("\n" + "-" * 80) + print("PLAYLIST ITEMS:") + print("-" * 80) + + for idx, item in enumerate(playlist_data.get('playlist', []), 1): + print(f"\n{idx}. File: {item.get('file_name', 'N/A')}") + print(f" URL: {item.get('url', 'N/A')}") + print(f" Duration: {item.get('duration', 'N/A')}s") + + # Check if URL is relative or absolute + url = item.get('url', '') + if url.startswith('http://') or url.startswith('https://'): + print(f" Type: Absolute URL") + else: + print(f" Type: Relative path (will fail to download!)") + + # Save full response + with open('server_response_debug.json', 'w') as f: + json.dump(playlist_data, f, indent=2) + print(f"\n✓ Full response saved to: server_response_debug.json") + + print("\n" + "=" * 80) + print("SUMMARY") + print("=" * 80) + print(f"Server has: {len(playlist_data.get('playlist', []))} files") + print(f"Local has: 3 files (from playlists/server_playlist_v8.json)") + + if len(playlist_data.get('playlist', [])) > 3: + print(f"\n⚠️ PROBLEM: Server has {len(playlist_data.get('playlist', []))} files but only 3 were saved!") + print("\nMissing files are likely:") + local_files = ['music.jpg', '130414-746934884.mp4', 'IMG_0386.jpeg'] + server_files = [item.get('file_name', '') for item in playlist_data.get('playlist', [])] + missing = [f for f in server_files if f not in local_files] + for f in missing: + print(f" - {f}") + +else: + print("✗ Failed to fetch playlist") + sys.exit(1) + +print("\n" + "=" * 80) diff --git a/working_files/test_direct_api.py b/working_files/test_direct_api.py new file mode 100644 index 0000000..5d958e6 --- /dev/null +++ b/working_files/test_direct_api.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Direct API test to check server playlist.""" + +import requests +import json + +# Try with the saved auth +auth_file = 'src/player_auth.json' +with open(auth_file, 'r') as f: + auth_data = json.load(f) + +server_url = auth_data['server_url'] +auth_code = auth_data['auth_code'] + +print("=" * 80) +print("DIRECT API TEST") +print("=" * 80) +print(f"Server: {server_url}") +print(f"Auth code: {auth_code[:20]}...") +print() + +# Try to get playlist +try: + url = f"{server_url}/api/player/playlist" + headers = { + 'Authorization': f'Bearer {auth_code}' + } + + print(f"Fetching: {url}") + response = requests.get(url, headers=headers, timeout=10) + + print(f"Status: {response.status_code}") + + if response.status_code == 200: + data = response.json() + print(f"\nPlaylist version: {data.get('playlist_version', 'N/A')}") + print(f"Number of items: {len(data.get('playlist', []))}") + + print("\nPlaylist items:") + for idx, item in enumerate(data.get('playlist', []), 1): + print(f"\n {idx}. {item.get('file_name', 'N/A')}") + print(f" URL: {item.get('url', 'N/A')}") + print(f" Duration: {item.get('duration', 'N/A')}s") + + # Save full response + with open('server_playlist_full.json', 'w') as f: + json.dump(data, f, indent=2) + print(f"\nFull response saved to: server_playlist_full.json") + else: + print(f"Error: {response.text}") + +except Exception as e: + print(f"Error: {e}") + +print("\n" + "=" * 80) diff --git a/working_files/test_playback_logging.py b/working_files/test_playback_logging.py new file mode 100644 index 0000000..4bce729 --- /dev/null +++ b/working_files/test_playback_logging.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +""" +Test script to verify the enhanced logging without running the full GUI +""" +import os +import json + +def simulate_playback_check(): + """Simulate the playback logic to see what would happen""" + + base_dir = os.path.dirname(os.path.abspath(__file__)) + media_dir = os.path.join(base_dir, 'media') + playlists_dir = os.path.join(base_dir, 'playlists') + + # Supported extensions + VIDEO_EXTENSIONS = ['.mp4', '.avi', '.mkv', '.mov', '.webm'] + IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.bmp', '.gif'] + + # Load playlist + playlist_file = os.path.join(playlists_dir, 'server_playlist_v8.json') + with open(playlist_file, 'r') as f: + data = json.load(f) + + playlist = data.get('playlist', []) + + print("=" * 80) + print("SIMULATING PLAYBACK SEQUENCE") + print("=" * 80) + + for idx, media_item in enumerate(playlist): + file_name = media_item.get('file_name', '') + duration = media_item.get('duration', 10) + + print(f"\n[STEP {idx + 1}] ===== Playing item {idx + 1}/{len(playlist)} =====") + print(f" File: {file_name}") + print(f" Duration: {duration}s") + + # Construct path + media_path = os.path.join(media_dir, file_name) + print(f" Full path: {media_path}") + + # Check existence + if not os.path.exists(media_path): + print(f" \u274c Media file not found: {media_path}") + print(f" ACTION: Skipping to next media...") + continue + + file_size = os.path.getsize(media_path) + print(f" \u2713 File exists (size: {file_size:,} bytes)") + + # Check extension + file_extension = os.path.splitext(file_name)[1].lower() + print(f" Extension: {file_extension}") + + if file_extension in VIDEO_EXTENSIONS: + print(f" Media type: VIDEO") + print(f" ACTION: play_video('{media_path}', {duration})") + print(f" - Creating Video widget...") + print(f" - Adding to content area...") + print(f" - Scheduling next media in {duration}s") + print(f" \u2713 Media started successfully") + elif file_extension in IMAGE_EXTENSIONS: + print(f" Media type: IMAGE") + print(f" ACTION: play_image('{media_path}', {duration})") + print(f" - Creating AsyncImage widget...") + print(f" - Adding to content area...") + print(f" - Scheduling next media in {duration}s") + print(f" \u2713 Image displayed successfully") + else: + print(f" \u274c Unsupported media type: {file_extension}") + print(f" Supported: .mp4/.avi/.mkv/.mov/.webm/.jpg/.jpeg/.png/.bmp/.gif") + print(f" ACTION: Skipping to next media...") + continue + + print(f"\n [After {duration}s] Transitioning to next media (was index {idx})") + + print("\n" + "=" * 80) + print("END OF PLAYLIST - Would restart from beginning") + print("=" * 80) + +if __name__ == '__main__': + simulate_playback_check() diff --git a/working_files/test_server_playlist.py b/working_files/test_server_playlist.py new file mode 100644 index 0000000..69dff46 --- /dev/null +++ b/working_files/test_server_playlist.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Test script to check what playlist the server is actually returning.""" + +import json +import sys +import os + +# Add src directory to path +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src')) + +from get_playlists_v2 import fetch_server_playlist + +# Load config +config_file = 'config/app_config.json' +with open(config_file, 'r') as f: + config = json.load(f) + +print("=" * 80) +print("TESTING SERVER PLAYLIST FETCH") +print("=" * 80) + +# Fetch playlist from server +print("\n1. Fetching playlist from server...") +server_data = fetch_server_playlist(config) + +print(f"\n2. Server Response:") +print(f" Version: {server_data.get('version', 'N/A')}") +print(f" Playlist items: {len(server_data.get('playlist', []))}") + +print(f"\n3. Detailed Playlist Items:") +for idx, item in enumerate(server_data.get('playlist', []), 1): + print(f"\n Item {idx}:") + print(f" file_name: {item.get('file_name', 'N/A')}") + print(f" url: {item.get('url', 'N/A')}") + print(f" duration: {item.get('duration', 'N/A')}") + +print("\n" + "=" * 80) +print(f"TOTAL: Server has {len(server_data.get('playlist', []))} files") +print("=" * 80) + +# Save to file for inspection +output_file = 'server_response_debug.json' +with open(output_file, 'w') as f: + json.dump(server_data, f, indent=2) +print(f"\nFull server response saved to: {output_file}")