Compare commits
17 Commits
0.1.7
...
v0.1.9-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43adf12755 | ||
|
|
e2bea2c151 | ||
|
|
804b2bfe7e | ||
|
|
4c675e814c | ||
|
|
1509c37cd9 | ||
|
|
24ba4a0b70 | ||
|
|
7a7ef8d7bc | ||
|
|
be9a72701e | ||
|
|
c5de8d9841 | ||
|
|
671b813e6c | ||
|
|
b29a7bbbe3 | ||
|
|
d4c3a67249 | ||
|
|
4be027bb35 | ||
|
|
39e44e0606 | ||
|
|
8c30c6183d | ||
|
|
9e7f331c49 | ||
|
|
bb856c96a1 |
@@ -1,5 +1,5 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 0.1.7
|
current_version = 0.1.8
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
|
|
||||||
|
|||||||
11
.github/workflows/main.yml
vendored
11
.github/workflows/main.yml
vendored
@@ -1,9 +1,12 @@
|
|||||||
name: Build and Release on Tag
|
name: Build and Release on Tag
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
branches:
|
inputs:
|
||||||
- main
|
branch:
|
||||||
|
description: 'Branch to build the Docker image from'
|
||||||
|
required: true
|
||||||
|
default: 'main'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-publish:
|
build-and-publish:
|
||||||
@@ -73,5 +76,7 @@ jobs:
|
|||||||
name: Release ${{ env.VERSION }}
|
name: Release ${{ env.VERSION }}
|
||||||
body: |
|
body: |
|
||||||
${{ env.CHANGELOG_CONTENT }}
|
${{ env.CHANGELOG_CONTENT }}
|
||||||
|
generate_release_notes: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
22
.github/workflows/manual-build.yml
vendored
22
.github/workflows/manual-build.yml
vendored
@@ -18,12 +18,6 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.branch }}
|
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
|
# Extract branch name and latest commit SHA
|
||||||
- name: Extract branch name and commit SHA
|
- name: Extract branch name and commit SHA
|
||||||
id: branch_info
|
id: branch_info
|
||||||
@@ -35,6 +29,11 @@ jobs:
|
|||||||
- name: Create DEV_BUILD file
|
- name: Create DEV_BUILD file
|
||||||
run: |
|
run: |
|
||||||
echo "${{ env.BRANCH_NAME }}-${{ env.COMMIT_SHA }}" > DEV_BUILD
|
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
|
# Set up Docker
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -56,7 +55,16 @@ jobs:
|
|||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/${{ github.repository }}:${{ github.sha }}
|
ghcr.io/${{ github.repository }}:${{ env.COMMIT_SHA }}
|
||||||
ghcr.io/${{ github.repository }}:dev
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -3,7 +3,8 @@ import re
|
|||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
|
|
||||||
from app.classes import AudioProfile
|
from app.classes import AudioProfile
|
||||||
from app import app
|
from app import app, functions, read_dev_build_file
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
filters = {}
|
filters = {}
|
||||||
|
|
||||||
@@ -55,6 +56,37 @@ def audioprofile(text: str, path: str) -> Markup:
|
|||||||
)
|
)
|
||||||
return Markup(audio_profile_html)
|
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"<span class='badge rounded-pill bg-primary'>{version}</span>")
|
||||||
|
# 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"<a href='{url}' target='_blank'><span class='badge rounded-pill text-bg-warning btn-pulsing' data-bs-toggle='tooltip' title='An update for the {branch} branch is available.'>{version}</span></a>")
|
||||||
|
else:
|
||||||
|
return Markup(f"<span class='badge rounded-pill text-bg-secondary'>{version}</span>")
|
||||||
|
else:
|
||||||
|
nra,url = functions.get_latest_release(version)
|
||||||
|
if nra:
|
||||||
|
return Markup(f"<a href='{url}' target='_blank'><span class='badge rounded-pill text-bg-warning btn-pulsing' data-bs-toggle='tooltip' title='An update is available.'>{version}</span></a>")
|
||||||
|
|
||||||
|
|
||||||
|
return Markup(f"<span class='badge rounded-pill text-bg-primary'>{version}</span>")
|
||||||
|
else:
|
||||||
|
return Markup(f"<span class='badge rounded-pill text-bg-info'>{version}</span>")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@template_filter('jellyfin_link')
|
@template_filter('jellyfin_link')
|
||||||
def jellyfin_link(jellyfin_id: str) -> Markup:
|
def jellyfin_link(jellyfin_id: str) -> Markup:
|
||||||
|
|||||||
@@ -207,3 +207,41 @@ def get_longest_substring(input_string):
|
|||||||
substrings = re.split(pattern, input_string)
|
substrings = re.split(pattern, input_string)
|
||||||
longest_substring = max(substrings, key=len, default="")
|
longest_substring = max(substrings, key=len, default="")
|
||||||
return longest_substring
|
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,''
|
||||||
@@ -129,7 +129,12 @@ def download_missing_tracks(self):
|
|||||||
failed_downloads = 0
|
failed_downloads = 0
|
||||||
for track in undownloaded_tracks:
|
for track in undownloaded_tracks:
|
||||||
app.logger.info(f"Processing track: {track.name} [{track.provider_track_id}]")
|
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
|
# Check if the track already exists in the output directory
|
||||||
file_path = f"{output_dir.replace('{track-id}', track.provider_track_id)}.mp3"
|
file_path = f"{output_dir.replace('{track-id}', track.provider_track_id)}.mp3"
|
||||||
# region search before download
|
# region search before download
|
||||||
@@ -229,7 +234,7 @@ def download_missing_tracks(self):
|
|||||||
progress = (processed_tracks / total_tracks) * 100
|
progress = (processed_tracks / total_tracks) * 100
|
||||||
db.session.commit()
|
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,
|
'current': processed_tracks,
|
||||||
'total': total_tracks,
|
'total': total_tracks,
|
||||||
'percent': progress,
|
'percent': progress,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.7"
|
__version__ = "0.1.8"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Whats up in Jellyplist 0.1.6?
|
# Whats up in Jellyplist 0.1.7?
|
||||||
### Major overhaul
|
### 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´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.
|
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
|
# 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
|
### 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 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
|
- UI/UX: The Search bar now works with the new API implementation
|
||||||
|
|||||||
15
changelogs/0.1.8.md
Normal file
15
changelogs/0.1.8.md
Normal file
@@ -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
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class Config:
|
|||||||
LIDARR_URL = os.getenv('LIDARR_URL','')
|
LIDARR_URL = os.getenv('LIDARR_URL','')
|
||||||
LIDARR_MONITOR_ARTISTS = os.getenv('LIDARR_MONITOR_ARTISTS','false').lower() == 'true'
|
LIDARR_MONITOR_ARTISTS = os.getenv('LIDARR_MONITOR_ARTISTS','false').lower() == 'true'
|
||||||
MUSIC_STORAGE_BASE_PATH = os.getenv('MUSIC_STORAGE_BASE_PATH')
|
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 specific configuration
|
||||||
SPOTDL_CONFIG = {
|
SPOTDL_CONFIG = {
|
||||||
'cookie_file': '/jellyplist/cookies.txt',
|
'cookie_file': '/jellyplist/cookies.txt',
|
||||||
@@ -41,7 +41,9 @@ class Config:
|
|||||||
'threads': 12
|
'threads': 12
|
||||||
}
|
}
|
||||||
if os.getenv('MUSIC_STORAGE_BASE_PATH'):
|
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
|
@classmethod
|
||||||
def validate_env_vars(cls):
|
def validate_env_vars(cls):
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ body {
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-bar {
|
.top-bar {
|
||||||
background-color: #1a1d21;
|
background-color: #1a1d21;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar h3 {
|
.sidebar h3 {
|
||||||
color: white;
|
color: white;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
@@ -50,47 +52,51 @@ body {
|
|||||||
width: 140px;
|
width: 140px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
.logo img{
|
|
||||||
|
.logo img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1600px) {
|
@media screen and (min-width: 1600px) {
|
||||||
.modal-dialog {
|
.modal-dialog {
|
||||||
max-width: 90%; /* New width for default modal */
|
max-width: 90%;
|
||||||
|
/* New width for default modal */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.searchbar{
|
|
||||||
|
.searchbar {
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background-color: #353b48;
|
background-color: #353b48;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_input{
|
.search_input {
|
||||||
color: white;
|
color: white;
|
||||||
border: 0;
|
border: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
background: none;
|
background: none;
|
||||||
width: 450px;
|
width: 450px;
|
||||||
caret-color:transparent;
|
caret-color: transparent;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
transition: width 0.4s linear;
|
transition: width 0.4s linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchbar:hover > .search_input{
|
.searchbar:hover>.search_input {
|
||||||
/* padding: 0 10px; */
|
/* padding: 0 10px; */
|
||||||
width: 450px;
|
width: 450px;
|
||||||
caret-color:red;
|
caret-color: red;
|
||||||
/* transition: width 0.4s linear; */
|
/* transition: width 0.4s linear; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchbar:hover > .search_icon{
|
.searchbar:hover>.search_icon {
|
||||||
background: white;
|
background: white;
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_icon{
|
.search_icon {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
float: right;
|
float: right;
|
||||||
@@ -98,6 +104,24 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
color:white;
|
color: white;
|
||||||
text-decoration:none;
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-pulsing {
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<span class="fixed-bottom m-3">{{version}}</span>
|
<span class="fixed-bottom m-3 ms-5">{{version | version_check}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main content with toggle button for mobile sidebar -->
|
<!-- Main content with toggle button for mobile sidebar -->
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.1.7"
|
__version__ = "0.1.8"
|
||||||
|
|||||||
Reference in New Issue
Block a user