Author SHA1 Message Date
copilot-swe-agent[bot]andnmemmert f0d9b06ef4 Add validation for station selection to require at least one station
Co-authored-by: nmemmert <67961292+nmemmert@users.noreply.github.com>
2025-11-28 16:31:36 +00:00
copilot-swe-agent[bot]andnmemmert 013edb952f Add station selection feature to SetupWizard and scrape function
Co-authored-by: nmemmert <67961292+nmemmert@users.noreply.github.com>
2025-11-28 16:29:29 +00:00
copilot-swe-agent[bot] d5da0f710e Initial plan 2025-11-28 16:25:25 +00:00
nmemmert 2d027d339a Use custom icon.png and fix buy list path to use absolute path 2025-11-17 14:04:24 -05:00
nmemmert dd985796e6 Fix Spirit FM parsing - bypass Cloudflare and use direct text file 2025-11-17 13:47:41 -05:00
nmemmert e762c4252d Fix song scraping - use proper HTML structure parsing 2025-11-17 13:41:25 -05:00
nmemmert b50b430a5f Switch back to Chrome webdriver, user should run with xvfb-run for headless support 2025-11-17 13:12:14 -05:00
nmemmert 8b68b382ff Switch to Firefox webdriver to avoid Chrome DevTools issues 2025-11-17 13:09:16 -05:00
nmemmert 8393e5d659 Fix Selenium Chrome session issues by simplifying options and adding error handling 2025-11-17 13:01:35 -05:00
nmemmert bf7aa42460 Improve song parsing for Journey FM and Spirit FM, fix app startup issues 2025-11-17 12:56:21 -05:00
nmemmert 59689dcbf6 windows 2025-11-17 12:18:25 -05:00
nmemmert 38131fb653 Fix buy list label to update on filter 2025-11-17 12:11:24 -05:00
nmemmert 5066b0b5e9 Fix syntax error: add missing newline after return statement 2025-11-17 12:05:47 -05:00
nmemmert cdb6e53fb6 Add debug prints to track GUI startup progress 2025-11-17 12:05:02 -05:00
nmemmert f89b970c94 Add display environment check and better error handling for GUI startup 2025-11-17 12:03:53 -05:00
nmemmert 62174895ff Add source tracking for scraped songs and display station in logs and history 2025-11-17 12:02:54 -05:00
nmemmert 723e12fe11 Modify buy list generation to append new missing songs instead of overwriting 2025-11-17 12:01:37 -05:00
nmemmert 2dcd4be86d Add missing QSplitter import to QtWidgets 2025-11-17 11:59:19 -05:00
nmemmert fbe0c4a8e1 Fix QAction import - move from QtWidgets to QtGui 2025-11-17 11:58:35 -05:00
nmemmert 30c2e1c907 Fix matplotlib backend import for PySide6 (Qt6) compatibility 2025-11-17 11:57:40 -05:00
nmemmert 32d87cf0c2 Add scraping from Spirit FM recently played list, combine with Journey FM and deduplicate songs 2025-11-17 11:56:26 -05:00
nmemmert 7e7754f533 Add Playlist Analytics Dashboard with charts for growth, top artists, and update frequency using matplotlib 2025-11-17 11:53:32 -05:00
nmemmert 1339365941 Add auto-playlist tagging: automatically add 'Journey FM' label and 5-star rating to songs added to Plex playlist 2025-11-17 11:49:27 -05:00
nmemmert 43e93c5abc Split artist and song into separate columns in history table 2025-11-17 11:46:15 -05:00
nmemmert eb61051c7c Redesign history table to show one song per row for better readability 2025-11-17 11:44:52 -05:00
nmemmert 3d18efb88f Improve history dialog to show full list of added and missing songs with word wrap and auto row sizing 2025-11-17 11:43:29 -05:00
nmemmert f958ea0983 Fix QListWidgetItem checkState usage to use Qt.CheckState enum instead of int 2025-11-17 11:40:44 -05:00
nmemmert 2da02dc7df Add debug message and better error handling for buy list dialog 2025-11-17 11:39:52 -05:00
nmemmert 3f377e165d Fix buy list dialog visibility by ensuring it shows on top and add song count label 2025-11-17 11:37:26 -05:00
nmemmert e3085f22ae Add playlist export, enhanced buy list with search/filter/remove, and statistics dashboard 2025-11-17 11:32:25 -05:00
nmemmert e643bb3c98 Add playlist history tracking with SQLite database and GUI dialog 2025-11-17 11:30:20 -05:00
nmemmert 9a90811dea Change to QTextBrowser for better HTML and link display 2025-11-17 11:24:51 -05:00
nmemmert f12b50f9d0 Fix buy list dialog to show properly with modal exec and proper HTML 2025-11-17 11:24:06 -05:00
nmemmert 589cbca2f6 Add debug message to buy list button 2025-11-17 11:23:25 -05:00
nmemmert 3ff79549d9 Add buy list button and dialog to GUI 2025-11-17 11:22:23 -05:00
nmemmert e74845cab6 Fix Amazon URL encoding and update buy list 2025-11-17 11:21:01 -05:00
nmemmert 2893487134 Add Amazon buy list feature for missing songs 2025-11-17 11:17:43 -05:00
nmemmert be9a1a1b64 linux fix app launch 2025-11-17 09:29:21 -05:00
nmemmert d5f2aa0dfd Update README with Linux installer and detailed Plex token instructions; add help button in app for Plex token 2025-11-17 09:26:47 -05:00
nmemmert d357405fb7 Merge branch 'linux' 2025-11-17 09:24:35 -05:00
nmemmert 5f88cdd92b Fix ChromeDriver compatibility and update desktop icon 2025-11-17 09:24:16 -05:00
nmemmert 9b63031c1e Add Linux installer script 2025-11-17 09:18:32 -05:00
nmemmert 5e891ad139 Update Linux app to use Firefox and auto-install dependencies 2025-11-17 08:24:50 -05:00
Nate Emmert c2b91f3f47 windows updaate 2025-11-17 08:22:25 -05:00
Nate Emmert ce5175d8db Add desktop GUI application with system tray support
- Created PySide6-based desktop application
- Added system tray integration for Windows and Linux
- Built-in log viewer and configuration wizard
- Easy setup and automatic playlist updates
- Cross-platform compatibility
- Updated README with comprehensive instructions
2025-11-17 08:08:20 -05:00
nmemmert 345c40f40d Update main.py with a new script 2025-11-15 08:49:41 -05:00
13 changed files with 1934 additions and 109 deletions

No files matched your search

+204 -18
View File
@@ -1,28 +1,214 @@
# Journey FM Playlist Creator
This script automates the process of creating a Plex playlist from recently played songs on My Journey FM radio station.
A cross-platform desktop application that automatically creates Plex playlists from recently played songs on My Journey FM radio station.
## Features
![Application Screenshot](screenshot.png)
- Scrapes recently played songs from https://www.myjourneyfm.com/recently-played/
- Checks your local music library for matching songs
- Creates a playlist in Plex with the found songs
## ✨ Features
## Setup
- **System Tray Integration**: Runs minimized in the taskbar/system tray
- **Automatic Updates**: Configurable automatic playlist updates
- **Cross-Platform**: Works on Windows and Linux
- **Log Viewer**: Built-in log viewer for monitoring updates
- **Easy Setup**: Guided setup wizard for initial configuration
- **Duplicate Prevention**: Smart duplicate detection and prevention
- **Artist Matching**: Handles featured artists, punctuation, and name variations
1. Install dependencies: `pip install -r requirements.txt`
2. Configure the script:
- Set `PLEX_URL` to your Plex server URL
- Set `PLEX_TOKEN` to your Plex authentication token
- Set `MUSIC_LIBRARY_PATH` to the path of your music library
3. Run the script: `python main.py`
## 🚀 Quick Start
## Requirements
### Windows
- Python 3.6+
- Plex server
- Local music library with ID3 tags
1. **Install Dependencies**
```bash
pip install -r requirements.txt
```
## Note
2. **Create Application Icon**
```bash
python create_icon.py
```
The HTML scraping part may need adjustment based on the actual structure of the website. Inspect the page to find the correct selectors for artist and title.
3. **Run the Application**
```bash
python journey_fm_app.py
```
Or use the batch file: `run_app.bat`
### Linux
1. **Run the Installer**
```bash
./install.sh
```
This will:
- Install Python dependencies
- Set up virtual environment
- Install Chromium browser
- Create desktop icon
- Add to applications menu
2. **Launch from Menu**
- Search for "Journey FM Playlist" in your applications menu
- Or run: `./journey_fm_app.sh`
## ⚙️ Configuration
### First-Time Setup
When you first run the application, a setup wizard will guide you through:
1. **Plex Token**: See below for how to get your Plex token
2. **Server IP**: Your Plex server's local IP address (e.g., 192.168.1.100)
3. **Playlist Name**: Name for your Journey FM playlist (default: "Journey FM Recently Played")
4. **Auto-Update Settings**: Enable automatic updates and set intervals
#### How to Get Your Plex Token
1. Go to [https://plex.tv/claim](https://plex.tv/claim) in your web browser
2. Sign in with your Plex account
3. Copy the claim token from the page (it looks like: `pU-mHWYUZU6iXJFhJyA`)
4. Paste it into the application setup
**Note**: This token is used to authenticate with your Plex server. Keep it secure and don't share it.
### Manual Configuration
You can also edit settings anytime through the Settings menu in the application.
## 📱 System Tray Features
- **Minimize to Tray**: The app minimizes to system tray instead of closing
- **Right-click Menu**:
- Show: Restore the main window
- Update Now: Manually trigger playlist update
- Quit: Exit the application
- **Double-click**: Restore the main window
- **Notifications**: Get notified when automatic updates complete
## 📋 Log Viewer
The built-in log viewer shows:
- Update timestamps
- Songs added to playlist
- Error messages
- Update duration
- Connection status
Logs are automatically saved to `playlist_log.txt` for external viewing.
## 🔄 Automatic Updates
Configure automatic updates to run every:
- 5-120 minutes
- Hours (up to several hours)
The app will:
- Run in the background
- Show tray notifications when updates complete
- Prevent duplicate songs
- Handle connection errors gracefully
## 🛠️ Troubleshooting
### Common Issues
**"Server not found"**
- Ensure your Plex server is running
- Check that you've claimed the server at http://[SERVER_IP]:32400/web
- Verify the server IP address in settings
**"No matching tracks found"**
- Ensure your music library is properly scanned in Plex
- Check that song metadata matches (artist names, titles)
- The app handles variations like "&" vs "and", featured artists, etc.
**System tray not working**
- On Linux, ensure you have a system tray (like KDE/GNOME)
- Some desktop environments may need additional packages
### Manual Testing
Test the core functionality:
```bash
python main.py
```
This runs the update once without the GUI.
## 📁 Project Structure
```
journey-fm-playlist/
├── journey_fm_app.py # Main GUI application
├── main.py # Core update logic
├── create_icon.py # Icon generator
├── run_app.bat # Windows launcher
├── journey_fm_app.sh # Linux launcher
├── requirements.txt # Python dependencies
├── config.json # Configuration (auto-generated)
├── playlist_log.txt # Update logs
├── icon.png # Application icon
└── README.md # This file
```
## 🔧 Development
### Adding Features
The application is built with PyQt6 for cross-platform compatibility. Key components:
- `MainWindow`: Main application window
- `SystemTrayApp`: Tray icon and menu
- `SetupWizard`: Configuration wizard
- `LogViewer`: Log display widget
- `UpdateWorker`: Background update thread
### Building Standalone Executables
**Windows (PyInstaller)**
```bash
pip install pyinstaller
pyinstaller --onefile --windowed --icon=icon.ico journey_fm_app.py
```
**Linux (PyInstaller)**
```bash
pip install pyinstaller
pyinstaller --onefile --icon=icon.png journey_fm_app.py
```
## 📋 Requirements
- **Python**: 3.8+
- **PyQt6**: GUI framework
- **Selenium**: Web scraping
- **PlexAPI**: Plex integration
- **BeautifulSoup4**: HTML parsing
### System Requirements
- **Windows**: 10+ with system tray support
- **Linux**: Modern desktop environment with system tray
- **Plex Server**: Local or remote Plex server access
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test on both Windows and Linux
5. Submit a pull request
## 📄 License
This project is open source. Feel free to use and modify as needed.
## 🆘 Support
If you encounter issues:
1. Check the application logs
2. Verify your Plex server connection
3. Ensure all dependencies are installed
4. Test with `python main.py` first
For bugs or feature requests, please create an issue on GitHub.
+221
View File
@@ -0,0 +1,221 @@
Songs not in your library - Amazon search links:
Danny Gokey - I Got You
https://www.amazon.com/s?k=Danny%20Gokey%20I%20Got%20You&i=digital-music
Have For You Colton Dixon - The Love I
https://www.amazon.com/s?k=Have%20%20For%20%20You%20Colton%20%20Dixon%20The%20%20Love%20%20I%20&i=digital-music
Done Tasha Layton - Look What You've
https://www.amazon.com/s?k=Done%20Tasha%20%20Layton%20Look%20%20What%20%20You%27ve%20&i=digital-music
Cochren & Co. - Who Can
https://www.amazon.com/s?k=Cochren%20%26%20%20Co.%20Who%20%20Can&i=digital-music
Have For You Colton Dixon - The Love I
https://www.amazon.com/s?k=Have%20%20For%20%20You%20Colton%20%20Dixon%20The%20%20Love%20%20I%20&i=digital-music
Done Tasha Layton - Look What You've
https://www.amazon.com/s?k=Done%20Tasha%20%20Layton%20Look%20%20What%20%20You%27ve%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
Andrew Ripp - Breakdown
https://www.amazon.com/s?k=Andrew%20%20Ripp%20Breakdown&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Crowder - Still
https://www.amazon.com/s?k=Crowder%20Still&i=digital-music
Thing About Praise Benjamin William Hastings - That's The
https://www.amazon.com/s?k=Thing%20%20About%20%20Praise%20Benjamin%20%20William%20%20Hastings%20That%27s%20%20The%20&i=digital-music
Love Feat. Bear Rinehar We The Kingdom - Child Of
https://www.amazon.com/s?k=Love%20%20Feat.%20%20Bear%20%20Rinehar%20We%20%20The%20%20Kingdom%20Child%20%20Of%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Thing About Praise Benjamin William Hastings - That's The
https://www.amazon.com/s?k=Thing%20%20About%20%20Praise%20Benjamin%20%20William%20%20Hastings%20That%27s%20%20The%20&i=digital-music
Love Feat. Bear Rinehar We The Kingdom - Child Of
https://www.amazon.com/s?k=Love%20%20Feat.%20%20Bear%20%20Rinehar%20We%20%20The%20%20Kingdom%20Child%20%20Of%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
You've Done Tasha Layton - Look What
https://www.amazon.com/s?k=You%27ve%20%20Done%20Tasha%20%20Layton%20Look%20%20What%20&i=digital-music
Zach Williams - Jesus Loves
https://www.amazon.com/s?k=Zach%20Williams%20Jesus%20Loves&i=digital-music
Zach Williams - Jesus Loves
https://www.amazon.com/s?k=Zach%20%20Williams%20Jesus%20%20Loves&i=digital-music
Am They - Scars I
https://www.amazon.com/s?k=Am%20%20They%20Scars%20I%20&i=digital-music
Church Sing Tauren Wells - Let The
https://www.amazon.com/s?k=Church%20%20Sing%20Tauren%20%20Wells%20Let%20%20The%20&i=digital-music
You Danny Gokey - I Got
https://www.amazon.com/s?k=You%20Danny%20%20Gokey%20I%20%20Got%20&i=digital-music
Cochren & Co. - Church (Take Me Back)
https://www.amazon.com/s?k=Cochren%20%26%20Co.%20Church%20%28Take%20Me%20Back%29&i=digital-music
Blessing Offor - Your Love
https://www.amazon.com/s?k=Blessing%20Offor%20Your%20Love&i=digital-music
Anne Wilson - My Jesus
https://www.amazon.com/s?k=Anne%20Wilson%20My%20Jesus&i=digital-music
Sidewalk Prophets - You Love Me Anyway
https://www.amazon.com/s?k=Sidewalk%20Prophets%20You%20Love%20Me%20Anyway&i=digital-music
Casting Crowns (ft Matthew West) - Nobody
https://www.amazon.com/s?k=Casting%20Crowns%20%28ft%20Matthew%20West%29%20Nobody&i=digital-music
Phil Wickham - This Is Amazing Grace
https://www.amazon.com/s?k=Phil%20Wickham%20This%20Is%20Amazing%20Grace&i=digital-music
Crowder - STILL
https://www.amazon.com/s?k=Crowder%20STILL&i=digital-music
Josh Baldwin - Made for More
https://www.amazon.com/s?k=Josh%20Baldwin%20Made%20for%20More&i=digital-music
Leanna Crawford - Honest
https://www.amazon.com/s?k=Leanna%20Crawford%20Honest&i=digital-music
We The Kingdom - God So Loved
https://www.amazon.com/s?k=We%20The%20Kingdom%20God%20So%20Loved&i=digital-music
Dan Bremnes - Call Your Name
https://www.amazon.com/s?k=Dan%20Bremnes%20Call%20Your%20Name&i=digital-music
Big Daddy Weave - I Know
https://www.amazon.com/s?k=Big%20Daddy%20Weave%20I%20Know&i=digital-music
The Afters - God Is With Us
https://www.amazon.com/s?k=The%20Afters%20God%20Is%20With%20Us&i=digital-music
CAIN - Rise Up (Lazarus)
https://www.amazon.com/s?k=CAIN%20Rise%20Up%20%28Lazarus%29&i=digital-music
Katy Nichole - Have Your Way
https://www.amazon.com/s?k=Katy%20Nichole%20Have%20Your%20Way&i=digital-music
Ryan Stevenson - Preach
https://www.amazon.com/s?k=Ryan%20Stevenson%20Preach&i=digital-music
Elevation Worship - Praise
https://www.amazon.com/s?k=Elevation%20Worship%20Praise&i=digital-music
Andrew Ripp - Breakdown
https://www.amazon.com/s?k=Andrew%20Ripp%20Breakdown&i=digital-music
Chris Tomlin - Amazing Grace (My Chains Are Gon
https://www.amazon.com/s?k=Chris%20Tomlin%20Amazing%20Grace%20%28My%20Chains%20Are%20Gon&i=digital-music
Tauren Wells - Known
https://www.amazon.com/s?k=Tauren%20Wells%20Known&i=digital-music
Big Daddy Weave - All Things New (Single Mix)
https://www.amazon.com/s?k=Big%20Daddy%20Weave%20All%20Things%20New%20%28Single%20Mix%29&i=digital-music
+27
View File
@@ -0,0 +1,27 @@
2025-11-17 12:40:16.417598: Starting Journey FM Playlist app...
2025-11-17 12:40:16.417787: DISPLAY set, initializing GUI
2025-11-17 12:40:16.553130: Failed to start GUI application: 'MainWindow' object has no attribute 'update_playlist'
2025-11-17 12:42:52.626588: Starting Journey FM Playlist app...
2025-11-17 12:42:52.626688: DISPLAY set, initializing GUI
2025-11-17 12:42:52.761510: App initialized, starting event loop
2025-11-17 12:56:44.139926: Starting Journey FM Playlist app...
2025-11-17 12:56:44.140042: DISPLAY set, initializing GUI
2025-11-17 12:56:44.381412: App initialized, starting event loop
2025-11-17 13:01:57.291192: Starting Journey FM Playlist app...
2025-11-17 13:01:57.291370: DISPLAY set, initializing GUI
2025-11-17 13:01:57.440291: App initialized, starting event loop
2025-11-17 13:09:29.113472: Starting Journey FM Playlist app...
2025-11-17 13:09:29.113563: DISPLAY set, initializing GUI
2025-11-17 13:09:29.245236: App initialized, starting event loop
2025-11-17 13:12:14.103261: Starting Journey FM Playlist app...
2025-11-17 13:12:14.103373: DISPLAY set, initializing GUI
2025-11-17 13:12:14.295725: App initialized, starting event loop
2025-11-17 13:48:09.668000: Starting Journey FM Playlist app...
2025-11-17 13:48:09.668117: DISPLAY set, initializing GUI
2025-11-17 13:48:10.016465: App initialized, starting event loop
2025-11-17 13:55:31.548889: Starting Journey FM Playlist app...
2025-11-17 13:55:31.549017: DISPLAY set, initializing GUI
2025-11-17 13:55:31.715072: App initialized, starting event loop
2025-11-17 14:02:10.571449: Starting Journey FM Playlist app...
2025-11-17 14:02:10.571573: DISPLAY set, initializing GUI
2025-11-17 14:02:10.700170: App initialized, starting event loop
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""
Create application icon
"""
from PySide6.QtGui import QIcon, QPixmap, QPainter, QColor, QFont
from PySide6.QtCore import QSize
def create_icon():
"""Create a simple application icon"""
pixmap = QPixmap(64, 64)
pixmap.fill(QColor(30, 144, 255)) # Dodger blue background
painter = QPainter(pixmap)
painter.setPen(QColor(255, 255, 255))
painter.setFont(QFont("Arial", 32, QFont.Weight.Bold))
# Draw music note symbol
painter.drawText(16, 48, "♪")
painter.end()
icon = QIcon(pixmap)
return icon
if __name__ == "__main__":
from PySide6.QtWidgets import QApplication
import sys
app = QApplication(sys.argv)
icon = create_icon()
pixmap = icon.pixmap(QSize(64, 64))
pixmap.save("icon.png")
print("Icon created: icon.png")
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Executable
+59
View File
@@ -0,0 +1,59 @@
#!/bin/bash
# Journey FM Playlist Creator - Linux Installer
echo "Journey FM Playlist Creator - Linux Installer"
echo "=============================================="
# Get the directory where this script is located
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
echo "Installing in: $DIR"
# Check if Python 3 is installed
if ! command -v python3 > /dev/null 2>&1; then
echo "Python 3 not found. Installing Python 3..."
sudo apt update && sudo apt install -y python3 python3-pip python3-venv
fi
# Create virtual environment
echo "Creating virtual environment..."
python3 -m venv "$DIR/.venv"
# Activate virtual environment
source "$DIR/.venv/bin/activate"
# Install Python dependencies
echo "Installing Python dependencies..."
pip install -r "$DIR/requirements.txt"
# Install Chromium browser
if ! command -v chromium-browser > /dev/null 2>&1; then
echo "Installing Chromium browser..."
sudo apt update && sudo apt install -y chromium-browser
fi
# Create desktop entry
echo "Creating desktop entry..."
mkdir -p ~/.local/share/applications
cat > ~/.local/share/applications/journey-fm.desktop << EOF
[Desktop Entry]
Name=Journey FM Playlist
Exec=$DIR/journey_fm_app.sh
Icon=$DIR/icon.png
Type=Application
Categories=Utility;
EOF
chmod +x ~/.local/share/applications/journey-fm.desktop
# Update desktop database
echo "Updating desktop database..."
update-desktop-database ~/.local/share/applications
# Make scripts executable
chmod +x "$DIR/journey_fm_app.sh"
echo "Installation complete!"
echo "You can now find 'Journey FM Playlist' in your applications menu."
echo "Or run: $DIR/journey_fm_app.sh"
+1050
View File
File diff suppressed because it is too large. Load diff
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# Journey FM Playlist Creator - Linux Startup Script
# Get the directory where this script is located
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Create virtual environment if it doesn't exist
if [ ! -d "$DIR/.venv" ]; then
python3 -m venv "$DIR/.venv"
fi
# Activate virtual environment
source "$DIR/.venv/bin/activate"
# Install dependencies
pip install -r "$DIR/requirements.txt"
# Check if Chromium is installed, install if not
if ! command -v chromium-browser > /dev/null 2>&1; then
echo "Chromium not found. Installing Chromium..."
sudo apt update && sudo apt install -y chromium-browser
fi
# Run the application
cd "$DIR"
python3 journey_fm_app.py
+271 -90
View File
@@ -11,91 +11,141 @@ import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import os
from plexapi.myplex import MyPlexAccount
from mutagen.easyid3 import EasyID3
import re
from datetime import datetime
import json
import subprocess
import urllib.parse
import sqlite3
import urllib.parse
# Configuration
# Configuration (defaults, will be overridden by config.json)
PLEX_TOKEN = 'pU-m3HWYUZU6iXJFhJyA' # Your Plex.tv token
SERVER_IP = '172.16.16.106' # Your local server IP
PLAYLIST_NAME = 'Journey FM Recently Played'
def scrape_recently_played():
"""Scrape recently played songs from myjourneyfm.com using Selenium"""
def scrape_recently_played(selected_stations=None):
"""Scrape recently played songs from Journey FM and Spirit FM"""
if selected_stations is None:
selected_stations = ['journey_fm', 'spirit_fm']
options = Options()
options.add_argument("--headless")
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--disable-gpu")
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.binary_location = "/usr/bin/google-chrome-stable"
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=options)
url = 'https://www.myjourneyfm.com/recently-played/'
driver.get(url)
# Wait for initial load
time.sleep(5)
# Click "View More" button if exists
try:
more_button = driver.find_element(By.ID, "moreSongs")
more_button.click()
time.sleep(5) # Wait for more songs to load
except:
pass # Button not found or already loaded
driver = webdriver.Chrome(service=service, options=options)
except Exception as e:
print(f"Failed to start Chrome: {e}")
return []
all_songs = []
seen = set()
# Journey FM - uses <strong> for title, normal text for artist
if 'journey_fm' in selected_stations:
try:
print("Scraping Journey FM...")
driver.get('https://www.myjourneyfm.com/recently-played/')
time.sleep(5)
# Click "View More" button if exists
try:
more_button = driver.find_element(By.ID, "moreSongs")
more_button.click()
time.sleep(5)
except:
pass
soup = BeautifulSoup(driver.page_source, 'html.parser')
# Find all song items using the proper class structure
song_items = soup.find_all('div', class_='rp-item')
for item in song_items:
try:
title_elem = item.find('h5', class_='song-title')
artist_elem = item.find('p', class_='song-artist')
if title_elem and artist_elem:
title = title_elem.get_text().strip()
artist = artist_elem.get_text().strip()
if title and artist:
key = (title.lower(), artist.lower())
if key not in seen:
all_songs.append({'title': title, 'artist': artist, 'source': 'Journey FM'})
seen.add(key)
print(f" Found: {title} by {artist}")
except Exception as e:
continue
except Exception as e:
print(f"Error scraping Journey FM: {e}")
# Spirit FM - loads songs from iframe text file
if 'spirit_fm' in selected_stations:
try:
print("Scraping Spirit FM...")
driver.get('https://spiritfm.com/ajax/now_playing_history.txt')
time.sleep(3)
# Parse the plain text format: "Mon 01:45PM Artist - Title"
soup = BeautifulSoup(driver.page_source, 'html.parser')
text_content = soup.get_text()
# Split into lines and parse each
for line in text_content.split('\n'):
line = line.strip()
if not line:
continue
# Match pattern: Day HH:MM(AM/PM) Artist - Title
match = re.match(r'^\w+\s+\d+:\d+[AP]M\s+(.+?)\s+-\s+(.+)$', line)
if match:
artist = match.group(1).strip()
title = match.group(2).strip()
# Clean up HTML entities
artist = artist.replace('&amp;', '&')
title = title.replace('&amp;', '&')
if title and artist:
key = (title.lower(), artist.lower())
if key not in seen:
all_songs.append({'title': title, 'artist': artist, 'source': 'Spirit FM'})
seen.add(key)
print(f" Found: {title} by {artist}")
except Exception as e:
print(f"Error scraping Spirit FM: {e}")
html = driver.page_source
driver.quit()
# Optional: save HTML to file for debugging (comment out if not needed)
# with open('page.html', 'w', encoding='utf-8') as f:
# f.write(html)
soup = BeautifulSoup(html, 'html.parser')
songs = []
# Find elements (div, li) that contain time in their text
song_elements = soup.find_all(lambda tag: tag.name in ['div', 'li', 'p'] and re.search(r'\d+:\d+ [AP]M', tag.get_text()))
for element in song_elements:
spans = element.find_all('span')
if len(spans) >= 3:
title = spans[0].get_text().strip()
artist = spans[1].get_text().strip()
# Ignore time
songs.append({'artist': artist, 'title': title})
else:
# Fallback to text parsing
full_text = element.get_text().strip()
match = re.search(r'(\d+:\d+ [AP]M)', full_text)
if match:
play_time = match.group(1)
before = full_text.split(play_time)[0].strip()
if ' by ' in before:
title, artist = before.split(' by ', 1)
title = title.strip()
artist = artist.strip()
else:
# Split on capital letters
parts = re.findall(r'[A-Z][^A-Z]*', before)
if len(parts) == 4:
title = ' '.join(parts[:2])
artist = ' '.join(parts[2:])
else:
title = ' '.join(parts[:3])
artist = ' '.join(parts[3:])
if title and artist:
songs.append({'artist': artist, 'title': title})
return songs
print(f"\nTotal unique songs found: {len(all_songs)}")
return all_songs
def normalize_string(s):
"""Normalize string for comparison"""
return re.sub(r'[^\w\s]', '', s).lower().strip()
return re.sub(r'[^\\w\\s]', '', s).lower().strip()
def find_song_in_library(artist, title, library_path):
"""Check if song exists in local library"""
@@ -113,11 +163,13 @@ def find_song_in_library(artist, title, library_path):
return None
def create_playlist_in_plex(plex, songs, playlist_name):
"""Create playlist in Plex"""
"""Create playlist in Plex and return added count, added songs, and missing songs"""
# Assuming Plex has a music library named 'Music'
music_library = plex.library.section('Music')
tracks = []
seen_keys = set() # Track which ratingKeys we've already added to prevent duplicates within this run
missing = []
added_songs = []
for song in songs:
# Clean title and artist by removing text in parentheses and normalizing spaces
@@ -131,7 +183,7 @@ def create_playlist_in_plex(plex, songs, playlist_name):
continue
# Skip entries that are ONLY punctuation (but allow titles with punctuation in them)
if re.match(r'^[!?\s]+$', clean_title):
if re.match(r'^[!?\\\\s]+$', clean_title):
continue
# Remove only ! and ? for searching, but keep apostrophes
@@ -152,7 +204,7 @@ def create_playlist_in_plex(plex, songs, playlist_name):
search_artist_lower = search_artist.lower()
# Clean featured artists from search (e.g., "W/ Emerson Day", "feat. Someone")
search_artist_main = re.sub(r'\s+(w/|feat\.|featuring|ft\.).*', '', search_artist_lower).strip()
search_artist_main = re.sub(r'\s+(w/|feat\.||featuring|ft\.).*', '', search_artist_lower).strip()
# Normalize & and + in track artist as well
track_artist = re.sub(r'\s*[&+]\s*', ' and ', track_artist)
@@ -163,14 +215,15 @@ def create_playlist_in_plex(plex, songs, playlist_name):
search_artist_lower in track_artist or track_artist in search_artist_lower):
# Only add if we haven't already added this track in this run
if track.ratingKey not in seen_keys:
tracks.append(track)
tracks.append((track, song))
seen_keys.add(track.ratingKey)
matched = True
break
# If no artist match, skip this track (don't add wrong songs)
if not matched:
pass # Don't add if artist doesn't match # Take the first match
missing.append(song)
added = 0
if tracks:
try:
# Try to find existing playlist
@@ -179,36 +232,115 @@ def create_playlist_in_plex(plex, songs, playlist_name):
# Get existing track rating keys to avoid duplicates
existing_keys = {item.ratingKey for item in existing.items()}
# Filter out tracks already in playlist
new_tracks = [track for track in tracks if track.ratingKey not in existing_keys]
new_tracks = [track_song for track_song in tracks if track_song[0].ratingKey not in existing_keys]
if new_tracks:
existing.addItems(new_tracks)
print(f"Added {len(new_tracks)} new songs to existing playlist '{playlist_name}':")
for track in new_tracks:
print(f" + '{track.title}' by {track.artist().title}")
existing.addItems([ts[0] for ts in new_tracks])
for track, song in new_tracks:
# Auto-tag added songs
track.addLabel("Journey FM")
track.rate(5)
added = len(new_tracks)
added_songs.extend([f"{track.title} by {track.artist().title} ({song['source']})" for track, song in new_tracks])
else:
print(f"No new songs to add - all {len(tracks)} matching songs already in playlist.")
added = 0
except:
# Playlist doesn't exist, create it
plex.createPlaylist(playlist_name, tracks)
print(f"Created playlist '{playlist_name}' with {len(tracks)} songs:")
for track in tracks:
print(f" + '{track.title}' by {track.artist().title}")
plex.createPlaylist(playlist_name, [ts[0] for ts in tracks])
for track, song in tracks:
# Auto-tag added songs
track.addLabel("Journey FM")
track.rate(5)
added = len(tracks)
added_songs.extend([f"{track.title} by {track.artist().title} ({song['source']})" for track, song in tracks])
except Exception as e:
print(f"Error with playlist: {e}")
added = 0
added_songs = []
else:
print("No matching tracks found in Plex.")
added = 0
added_songs = []
return added, added_songs, missing
def prompt_for_config():
"""Prompt user for configuration variables"""
print("Configuration not found. Please provide the following:")
plex_token = input("Enter your Plex token: ").strip()
server_ip = input("Enter your Plex server IP: ").strip()
playlist_name = input("Enter the playlist name: ").strip()
config = {
'PLEX_TOKEN': plex_token,
'SERVER_IP': server_ip,
'PLAYLIST_NAME': playlist_name
}
with open('config.json', 'w') as f:
json.dump(config, f)
return config
def init_history_db():
"""Initialize the history database"""
conn = sqlite3.connect('playlist_history.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY,
date TEXT,
added_count INTEGER,
added_songs TEXT,
missing_count INTEGER,
missing_songs TEXT
)''')
conn.commit()
conn.close()
def save_history_entry(added_count, added_songs, missing_count, missing_songs):
"""Save a history entry to the database"""
conn = sqlite3.connect('playlist_history.db')
c = conn.cursor()
date = datetime.now().isoformat()
c.execute('INSERT INTO history (date, added_count, added_songs, missing_count, missing_songs) VALUES (?, ?, ?, ?, ?)',
(date, added_count, json.dumps(added_songs), missing_count, json.dumps(missing_songs)))
conn.commit()
conn.close()
def setup_scheduler(params):
"""Run the setup_scheduler.ps1 with parameters"""
cmd = ['powershell.exe', '-File', 'setup_scheduler.ps1']
for k, v in params.items():
cmd.extend(['-' + k, str(v)])
try:
subprocess.run(cmd, check=True)
print("Scheduler setup completed.")
except subprocess.CalledProcessError as e:
print(f"Scheduler setup failed: {e}")
def main():
global PLEX_TOKEN, SERVER_IP, PLAYLIST_NAME
# Load or prompt for config
selected_stations = ['journey_fm', 'spirit_fm'] # default
if os.path.exists('config.json'):
with open('config.json', 'r') as f:
config = json.load(f)
PLEX_TOKEN = config.get('PLEX_TOKEN', PLEX_TOKEN)
SERVER_IP = config.get('SERVER_IP', SERVER_IP)
PLAYLIST_NAME = config.get('PLAYLIST_NAME', PLAYLIST_NAME)
# Load selected stations
selected_stations_str = config.get('SELECTED_STATIONS', 'journey_fm,spirit_fm')
if isinstance(selected_stations_str, str) and selected_stations_str:
selected_stations = [s for s in selected_stations_str.split(',') if s]
# If empty or invalid, keep default
else:
# If no config file, use defaults (GUI should handle this)
pass
# Log start time
start_time = datetime.now()
print(f"\n{'='*60}")
print(f"Journey FM Playlist Update - {start_time.strftime('%Y-%m-%d %I:%M:%S %p')}")
print(f"{'='*60}\n")
# Scrape songs
songs = scrape_recently_played()
print(f"Found {len(songs)} recently played songs.")
# Initialize history database
init_history_db()
# Scrape songs with selected stations
songs = scrape_recently_played(selected_stations)
# Connect to Plex via MyPlexAccount
account = MyPlexAccount(token=PLEX_TOKEN)
@@ -225,20 +357,16 @@ def main():
break
if not server_resource:
print(f"Server at {SERVER_IP} not found in your claimed resources.")
print("Please claim the server: Open http://172.16.16.106:32400/web, sign in with your Plex account, and follow the claim prompts.")
return
plex = server_resource.connect()
print(f"Connected to server: {plex.friendlyName}, ID: {plex.machineIdentifier}")
# Debug: list library sections
print(f"Library sections: {[s.title for s in plex.library.sections()]}")
# Debug: list existing playlists (handle unicode safely)
playlists = plex.playlists()
playlist_names = [p.title.encode('ascii', 'ignore').decode('ascii') if not p.title.isascii() else p.title for p in playlists]
print(f"Existing playlists: {len(playlists)} total")
# Find matching songs in local library (optional, since Plex search might suffice)
# But to filter only those in local library, perhaps skip or adjust
@@ -246,15 +374,68 @@ def main():
# Create playlist
if songs:
create_playlist_in_plex(plex, songs, PLAYLIST_NAME)
added, added_songs, missing = create_playlist_in_plex(plex, songs, PLAYLIST_NAME)
else:
print("No songs found.")
added, added_songs, missing = 0, [], []
# Save to history
save_history_entry(added, added_songs, len(missing), missing)
# Create Amazon buy list for missing songs
if missing:
# Read existing buy list
existing_songs = set()
if os.path.exists('amazon_buy_list.txt'):
try:
with open('amazon_buy_list.txt', 'r') as f:
content = f.read()
lines = content.split('\n')
i = 0
while i < len(lines):
if lines[i].startswith('Songs not in your library'):
i += 2 # Skip header
continue
if lines[i].strip() and not lines[i].startswith('http'):
if ' - ' in lines[i]:
artist, title = lines[i].split(' - ', 1)
existing_songs.add((artist.strip(), title.strip()))
i += 1
except Exception as e:
pass
# Filter new missing songs
new_missing = []
for song in missing:
key = (song['artist'], song['title'])
if key not in existing_songs:
new_missing.append(song)
existing_songs.add(key)
if new_missing:
with open('amazon_buy_list.txt', 'a') as f: # Append mode
if not os.path.exists('amazon_buy_list.txt') or os.path.getsize('amazon_buy_list.txt') == 0:
f.write("Songs not in your library - Amazon search links:\n\n")
for song in new_missing:
artist = song['artist']
title = song['title']
# Create Amazon search URL
query = urllib.parse.quote(f"{artist} {title}")
url = f"https://www.amazon.com/s?k={query}&i=digital-music"
f.write(f"{artist} - {title}\n{url}\n\n")
else:
print("No new songs to add to buy list.")
print("Buy list updated.")
# Log completion
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
print(f"\nCompleted in {duration:.1f} seconds at {end_time.strftime('%I:%M:%S %p')}")
print(f"{'='*60}\n")
def update_playlist():
"""Function to update playlist, callable from GUI"""
main()
if __name__ == '__main__':
main()
main()
+28
View File
@@ -0,0 +1,28 @@
Requirement already satisfied: requests in ./.venv/lib/python3.13/site-packages (from -r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 1)) (2.32.5)
Requirement already satisfied: beautifulsoup4 in ./.venv/lib/python3.13/site-packages (from -r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 2)) (4.14.2)
Requirement already satisfied: plexapi in ./.venv/lib/python3.13/site-packages (from -r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 3)) (4.17.1)
Requirement already satisfied: mutagen in ./.venv/lib/python3.13/site-packages (from -r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 4)) (1.47.0)
Requirement already satisfied: selenium in ./.venv/lib/python3.13/site-packages (from -r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (4.38.0)
Requirement already satisfied: webdriver-manager in ./.venv/lib/python3.13/site-packages (from -r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 6)) (4.0.2)
Requirement already satisfied: PySide6 in ./.venv/lib/python3.13/site-packages (from -r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 7)) (6.10.0)
Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.13/site-packages (from requests->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 1)) (3.4.4)
Requirement already satisfied: idna<4,>=2.5 in ./.venv/lib/python3.13/site-packages (from requests->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 1)) (3.11)
Requirement already satisfied: urllib3<3,>=1.21.1 in ./.venv/lib/python3.13/site-packages (from requests->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 1)) (2.5.0)
Requirement already satisfied: certifi>=2017.4.17 in ./.venv/lib/python3.13/site-packages (from requests->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 1)) (2025.11.12)
Requirement already satisfied: soupsieve>1.2 in ./.venv/lib/python3.13/site-packages (from beautifulsoup4->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 2)) (2.8)
Requirement already satisfied: typing-extensions>=4.0.0 in ./.venv/lib/python3.13/site-packages (from beautifulsoup4->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 2)) (4.15.0)
Requirement already satisfied: trio<1.0,>=0.31.0 in ./.venv/lib/python3.13/site-packages (from selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (0.32.0)
Requirement already satisfied: trio-websocket<1.0,>=0.12.2 in ./.venv/lib/python3.13/site-packages (from selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (0.12.2)
Requirement already satisfied: websocket-client<2.0,>=1.8.0 in ./.venv/lib/python3.13/site-packages (from selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (1.9.0)
Requirement already satisfied: attrs>=23.2.0 in ./.venv/lib/python3.13/site-packages (from trio<1.0,>=0.31.0->selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (25.4.0)
Requirement already satisfied: sortedcontainers in ./.venv/lib/python3.13/site-packages (from trio<1.0,>=0.31.0->selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (2.4.0)
Requirement already satisfied: outcome in ./.venv/lib/python3.13/site-packages (from trio<1.0,>=0.31.0->selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (1.3.0.post0)
Requirement already satisfied: sniffio>=1.3.0 in ./.venv/lib/python3.13/site-packages (from trio<1.0,>=0.31.0->selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (1.3.1)
Requirement already satisfied: wsproto>=0.14 in ./.venv/lib/python3.13/site-packages (from trio-websocket<1.0,>=0.12.2->selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (1.3.1)
Requirement already satisfied: pysocks!=1.5.7,<2.0,>=1.5.6 in ./.venv/lib/python3.13/site-packages (from urllib3[socks]<3.0,>=2.5.0->selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (1.7.1)
Requirement already satisfied: python-dotenv in ./.venv/lib/python3.13/site-packages (from webdriver-manager->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 6)) (1.2.1)
Requirement already satisfied: packaging in ./.venv/lib/python3.13/site-packages (from webdriver-manager->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 6)) (25.0)
Requirement already satisfied: shiboken6==6.10.0 in ./.venv/lib/python3.13/site-packages (from PySide6->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 7)) (6.10.0)
Requirement already satisfied: PySide6_Essentials==6.10.0 in ./.venv/lib/python3.13/site-packages (from PySide6->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 7)) (6.10.0)
Requirement already satisfied: PySide6_Addons==6.10.0 in ./.venv/lib/python3.13/site-packages (from PySide6->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 7)) (6.10.0)
Requirement already satisfied: h11<1,>=0.16.0 in ./.venv/lib/python3.13/site-packages (from wsproto>=0.14->trio-websocket<1.0,>=0.12.2->selenium->-r /home/nmemmert/journey/journey-fm-playlist/requirements.txt (line 5)) (0.16.0)
Binary file not shown.
+3 -1
View File
@@ -3,4 +3,6 @@ beautifulsoup4
plexapi
mutagen
selenium
webdriver-manager
webdriver-manager
PySide6
matplotlib
+12
View File
@@ -0,0 +1,12 @@
@echo off
REM Journey FM Playlist Creator - Windows Launcher
cd /d "%~dp0"
REM Activate virtual environment if it exists
if exist .venv\Scripts\activate.bat (
call .venv\Scripts\activate.bat
)
REM Run the application without console window
pythonw journey_fm_app.py