diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 3fb8817..49df775 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.1.7 +current_version = 0.1.8 commit = True tag = True diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml index 3f5d82a..3f2de0b 100644 --- a/.github/workflows/manual-build.yml +++ b/.github/workflows/manual-build.yml @@ -18,12 +18,6 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.branch }} - - name: Extract Version - id: extract_version - run: | - version=$(python3 -c "import version; print(f'dev-{version.__version__}')") - echo "VERSION=$version" >> $GITHUB_ENV - # Extract branch name and latest commit SHA - name: Extract branch name and commit SHA id: branch_info @@ -35,6 +29,11 @@ jobs: - name: Create DEV_BUILD file run: | echo "${{ env.BRANCH_NAME }}-${{ env.COMMIT_SHA }}" > DEV_BUILD + - name: Extract Version + id: extract_version + run: | + version=$(python3 -c "import version; print(f'${{ env.BRANCH_NAME}}-{version.__version__}')") + echo "VERSION=$version" >> $GITHUB_ENV # Set up Docker - name: Set up Docker Buildx @@ -56,7 +55,16 @@ jobs: platforms: linux/amd64,linux/arm64 push: true tags: | - ghcr.io/${{ github.repository }}:${{ github.sha }} + ghcr.io/${{ github.repository }}:${{ env.COMMIT_SHA }} ghcr.io/${{ github.repository }}:dev + ghcr.io/${{ github.repository }}:${{ env.VERSION }} + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.VERSION }} + name: Dev Release ${{ env.VERSION }} + generate_release_notes: true + make_latest: false + \ No newline at end of file diff --git a/app/filters.py b/app/filters.py index f1ec19e..c4ff498 100644 --- a/app/filters.py +++ b/app/filters.py @@ -3,7 +3,8 @@ import re from markupsafe import Markup from app.classes import AudioProfile -from app import app +from app import app, functions, read_dev_build_file +from .version import __version__ filters = {} @@ -55,6 +56,37 @@ def audioprofile(text: str, path: str) -> Markup: ) return Markup(audio_profile_html) +@template_filter('version_check') +def version_check(version: str) -> Markup: + version = f"{__version__}{read_dev_build_file()}" + # if version contains a dash and the text after the dash is LOCAL, return version as a blue badge + if app.config['CHECK_FOR_UPDATES']: + if '-' in version and version.split('-')[1] == 'LOCAL': + return Markup(f"{version}") + # else if the version string contains a dash and the text after the dash is not LOCAL, check whether it contains another dash (like in e.g. v0.1.7-dev-89a1bc2) and split both parts + elif '-' in version and version.split('-')[1] != 'LOCAL' : + branch, commit_sha = version.split('-')[1], version.split('-')[2] + nra,url = functions.get_latest_dev_releases(branch_name = branch, commit_sha = commit_sha) + if nra: + return Markup(f"{version}") + else: + return Markup(f"{version}") + else: + nra,url = functions.get_latest_release(version) + if nra: + return Markup(f"{version}") + + + return Markup(f"{version}") + else: + return Markup(f"{version}") + + + + + + + @template_filter('jellyfin_link') def jellyfin_link(jellyfin_id: str) -> Markup: diff --git a/app/functions.py b/app/functions.py index f82805a..ea9ce53 100644 --- a/app/functions.py +++ b/app/functions.py @@ -206,4 +206,42 @@ def get_longest_substring(input_string): pattern = "[" + re.escape("".join(special_chars)) + "]" substrings = re.split(pattern, input_string) longest_substring = max(substrings, key=len, default="") - return longest_substring \ No newline at end of file + return longest_substring + +@cache.memoize(timeout=3600*2) +def get_latest_dev_releases(branch_name :str, commit_sha : str): + try: + response = requests.get('https://api.github.com/repos/kamilkosek/jellyplist/releases') + if response.status_code == 200: + data = response.json() + latest_release = None + for release in data: + if branch_name in release['tag_name']: + if latest_release is None or release['published_at'] > latest_release['published_at']: + latest_release = release + + if latest_release: + response = requests.get(f'https://api.github.com/repos/kamilkosek/jellyplist/git/ref/tags/{latest_release["tag_name"]}') + if response.status_code == 200: + data = response.json() + if commit_sha != data['object']['sha'][:7]: + return True, latest_release['html_url'] + + + return False, '' + except requests.exceptions.RequestException as e: + app.logger.error(f"Error fetching latest version: {str(e)}") + return False, '' + +@cache.memoize(timeout=3600*2) +def get_latest_release(tag_name :str): + try: + response = requests.get('https://api.github.com/repos/kamilkosek/jellyplist/releases/latest') + if response.status_code == 200: + data = response.json() + if data['tag_name'] != tag_name: + return True, data['html_url'] + return False, '' + except requests.exceptions.RequestException as e: + app.logger.error(f"Error fetching latest version: {str(e)}") + return False,'' \ No newline at end of file diff --git a/app/tasks.py b/app/tasks.py index 4baddb3..6ffd39e 100644 --- a/app/tasks.py +++ b/app/tasks.py @@ -129,7 +129,12 @@ def download_missing_tracks(self): failed_downloads = 0 for track in undownloaded_tracks: app.logger.info(f"Processing track: {track.name} [{track.provider_track_id}]") - + self.update_state(state=f'[{processed_tracks}/{total_tracks}] {track.name} [{track.provider_track_id}]', meta={ + 'current': processed_tracks, + 'total': total_tracks, + 'percent': (processed_tracks / total_tracks) * 100 if processed_tracks > 0 else 0, + 'failed': failed_downloads + }) # Check if the track already exists in the output directory file_path = f"{output_dir.replace('{track-id}', track.provider_track_id)}.mp3" # region search before download @@ -229,7 +234,7 @@ def download_missing_tracks(self): progress = (processed_tracks / total_tracks) * 100 db.session.commit() - self.update_state(state='PROGRESS', meta={ + self.update_state(state=f'[{processed_tracks}/{total_tracks}] {track.name} [{track.provider_track_id}]', meta={ 'current': processed_tracks, 'total': total_tracks, 'percent': progress, diff --git a/app/version.py b/app/version.py index f1380ee..9cb17e7 100644 --- a/app/version.py +++ b/app/version.py @@ -1 +1 @@ -__version__ = "0.1.7" +__version__ = "0.1.8" diff --git a/changelogs/0.1.7.md b/changelogs/0.1.7.md index 8655237..41ca7a0 100644 --- a/changelogs/0.1.7.md +++ b/changelogs/0.1.7.md @@ -1,4 +1,4 @@ -# Whats up in Jellyplist 0.1.6? +# Whats up in Jellyplist 0.1.7? ### Major overhaul I´ve been working the past week to make this project work again, after [Spotify announced to deprecate](https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api) the playlist discover API´s , which were a crucial part of this project. I also took this opportunity at the same time to do a major overhaul, on how Jellyplist gathers data from a music provider. Music provider API implementations must now implement defined abstract classes to work with Jellyplist, think of it like _plugins_. Jellyplist now, in theory, can gather data from any music provider - just the _plugins_ must be written. It also doesn´t matter, if it have 1,2 or 10 Music Providers to playlists. So stay tuned for more to come. @@ -50,6 +50,9 @@ MUSIC_STORAGE_BASE_PATH = '/storage/media/music' # The base path where # Must be the same value as your music library in jellyfin ``` +### ⚠️ Breaking change +As some table columns has been renamed, make sure to wipe your existing Jellyplist pgdata. Sorry for the inconvenience. + ### Other changes, improvements and fixes - UI/UX: The index page now has content. From there you can directly drop a playlist link - UI/UX: The Search bar now works with the new API implementation diff --git a/changelogs/0.1.8.md b/changelogs/0.1.8.md new file mode 100644 index 0000000..2602fdc --- /dev/null +++ b/changelogs/0.1.8.md @@ -0,0 +1,15 @@ +# Whats up in Jellyplist 0.1.8? +Not much this time, just some small fixes and one enhancement. + +### 🆕Jellyplist now checks for updates +Jellyplist now checks the GitHub releases for new version. +If a new version is available, you will notice the small badge on the lower left will pulsate slighty, so you don´t miss any new release :smile: + +If you don´t like that Jellyplist is doing this, you can opt out by setting this env var in your `.env` file +```bash +CHECK_FOR_UPDATES = false +``` + +### Other changes, improvements and fixes +- Fix for #30 , where the output path for spotDL wasn´t created correctly + \ No newline at end of file diff --git a/config.py b/config.py index 0b62eae..b996c99 100644 --- a/config.py +++ b/config.py @@ -32,7 +32,7 @@ class Config: LIDARR_URL = os.getenv('LIDARR_URL','') LIDARR_MONITOR_ARTISTS = os.getenv('LIDARR_MONITOR_ARTISTS','false').lower() == 'true' MUSIC_STORAGE_BASE_PATH = os.getenv('MUSIC_STORAGE_BASE_PATH') - + CHECK_FOR_UPDATES = os.getenv('CHECK_FOR_UPDATES','true').lower() == 'true' # SpotDL specific configuration SPOTDL_CONFIG = { 'cookie_file': '/jellyplist/cookies.txt', @@ -41,7 +41,9 @@ class Config: 'threads': 12 } if os.getenv('MUSIC_STORAGE_BASE_PATH'): - SPOTDL_CONFIG['output_file'] = os.path.join(MUSIC_STORAGE_BASE_PATH,'__jellyplist/{track-id}'), + + output_path = os.path.join(MUSIC_STORAGE_BASE_PATH,'__jellyplist/{track-id}') + SPOTDL_CONFIG.update({'output': output_path}) @classmethod def validate_env_vars(cls): diff --git a/static/css/styles.css b/static/css/styles.css index 3c1e918..7425b90 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -3,16 +3,18 @@ body { } .sidebar { - background-color: #1a1d21; + background-color: #1a1d21; height: 100vh; padding-top: 20px; padding-left: 10px; color: white; } + .top-bar { - background-color: #1a1d21; + background-color: #1a1d21; } + .sidebar h3 { color: white; padding-left: 15px; @@ -50,47 +52,51 @@ body { width: 140px; margin-right: 10px; } -.logo img{ + +.logo img { width: 100%; } + @media screen and (min-width: 1600px) { .modal-dialog { - max-width: 90%; /* New width for default modal */ + max-width: 90%; + /* New width for default modal */ } } -.searchbar{ + +.searchbar { margin-bottom: auto; margin-top: auto; height: 60px; background-color: #353b48; border-radius: 30px; padding: 10px; - } +} - .search_input{ +.search_input { color: white; border: 0; outline: 0; background: none; width: 450px; - caret-color:transparent; + caret-color: transparent; line-height: 40px; transition: width 0.4s linear; - } +} - .searchbar:hover > .search_input{ +.searchbar:hover>.search_input { /* padding: 0 10px; */ width: 450px; - caret-color:red; + caret-color: red; /* transition: width 0.4s linear; */ - } +} - .searchbar:hover > .search_icon{ +.searchbar:hover>.search_icon { background: white; color: #e74c3c; - } +} - .search_icon{ +.search_icon { height: 40px; width: 40px; float: right; @@ -98,6 +104,24 @@ body { justify-content: center; align-items: center; border-radius: 50%; - color:white; - text-decoration:none; - } \ No newline at end of file + color: white; + text-decoration: none; +} + +.btn-pulsing { + animation: pulse 2s infinite; + } + + @keyframes pulse { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(1.08); + } + + 100% { + transform: scale(1); + } + } \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 93bb6b7..53d6a38 100644 --- a/templates/base.html +++ b/templates/base.html @@ -116,7 +116,7 @@ - {{version}} + {{version | version_check}} diff --git a/version.py b/version.py index f1380ee..9cb17e7 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -__version__ = "0.1.7" +__version__ = "0.1.8"