diff --git a/app/__init__.py b/app/__init__.py
index 110a309..33a62bc 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -163,4 +163,10 @@ app.logger.debug(f"Debug logging active")
from app import routes
from app import jellyfin_routes, tasks
if "worker" in sys.argv:
- tasks.release_lock("download_missing_tracks_lock")
\ No newline at end of file
+ tasks.release_lock("download_missing_tracks_lock")
+
+from app import filters # Import the filters dictionary
+
+# Register all filters
+for name, func in filters.filters.items():
+ app.jinja_env.filters[name] = func
\ No newline at end of file
diff --git a/app/classes.py b/app/classes.py
new file mode 100644
index 0000000..56a9bec
--- /dev/null
+++ b/app/classes.py
@@ -0,0 +1,79 @@
+import subprocess
+import json
+from flask import current_app as app # Adjust this based on your app's structure
+from typing import Optional
+
+
+class AudioProfile:
+ def __init__(self, path: str, bitrate: int = 0, sample_rate: int = 0, channels: int = 0) -> None:
+ """
+ Initialize an AudioProfile instance.
+
+ Args:
+ path (str): The file path of the audio file.
+ bitrate (int): The audio bitrate in kbps. Default is 0.
+ sample_rate (int): The sample rate in Hz. Default is 0.
+ channels (int): The number of audio channels. Default is 0.
+ """
+ self.path: str = path
+ self.bitrate: int = bitrate # in kbps
+ self.sample_rate: int = sample_rate # in Hz
+ self.channels: int = channels
+
+ @staticmethod
+ def analyze_audio_quality_with_ffprobe(filepath: str) -> Optional['AudioProfile']:
+ """
+ Static method to analyze audio quality using ffprobe and return an AudioProfile instance.
+
+ Args:
+ filepath (str): Path to the audio file to analyze.
+
+ Returns:
+ Optional[AudioProfile]: An instance of AudioProfile if analysis is successful, None otherwise.
+ """
+ try:
+ # ffprobe command to extract bitrate, sample rate, and channel count
+ cmd = [
+ 'ffprobe', '-v', 'error', '-select_streams', 'a:0',
+ '-show_entries', 'stream=bit_rate,sample_rate,channels',
+ '-show_format',
+ '-of', 'json', filepath
+ ]
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ if result.returncode != 0:
+ app.logger.error(f"ffprobe error for file {filepath}: {result.stderr}")
+ return None
+
+ # Parse ffprobe output
+ data = json.loads(result.stdout)
+ stream = data.get('streams', [{}])[0]
+ bitrate: int = int(stream.get('bit_rate', 0)) // 1000 # Convert to kbps
+ if bitrate == 0: # Fallback if no bit_rate in stream
+ bitrate = int(data.get('format').get('bit_rate', 0)) // 1000
+ sample_rate: int = int(stream.get('sample_rate', 0)) # Hz
+ channels: int = int(stream.get('channels', 0))
+
+ # Create an AudioProfile instance
+ return AudioProfile(filepath, bitrate, sample_rate, channels)
+ except Exception as e:
+ app.logger.error(f"Error analyzing audio quality with ffprobe: {str(e)}")
+ return None
+
+ def compute_quality_score(self) -> int:
+ """
+ Compute a quality score based on bitrate, sample rate, and channels.
+
+ Returns:
+ int: The computed quality score.
+ """
+ return self.bitrate + (self.sample_rate // 1000) + (self.channels * 10)
+
+ def __repr__(self) -> str:
+ """
+ Representation of the AudioProfile instance.
+
+ Returns:
+ str: A string representation of the AudioProfile instance.
+ """
+ return (f"AudioProfile(path='{self.path}', bitrate={self.bitrate} kbps, "
+ f"sample_rate={self.sample_rate} Hz, channels={self.channels})")
diff --git a/app/filters.py b/app/filters.py
new file mode 100644
index 0000000..1bc4ec7
--- /dev/null
+++ b/app/filters.py
@@ -0,0 +1,48 @@
+import os
+import re
+from markupsafe import Markup
+
+from app.classes import AudioProfile
+
+filters = {}
+
+def template_filter(name):
+ """Decorator to register a Jinja2 filter."""
+ def decorator(func):
+ filters[name] = func
+ return func
+ return decorator
+
+@template_filter('highlight')
+def highlight_search(text, search_query):
+ if not search_query:
+ return text
+ search_query_escaped = re.escape(search_query)
+ highlighted_text = re.sub(
+ f"({search_query_escaped})",
+ r'\1',
+ text,
+ flags=re.IGNORECASE
+ )
+ return Markup(highlighted_text)
+
+
+@template_filter('audioprofile')
+def audioprofile(text: str, path: str) -> Markup:
+ if not path or not os.path.exists(path):
+ return Markup() # Return the original text if the file does not exist
+
+ # Create the AudioProfile instance using the static method
+ audio_profile = AudioProfile.analyze_audio_quality_with_ffprobe(path)
+ if not audio_profile:
+ return Markup(f"ERROR")
+
+ # Create a nicely formatted HTML representation
+ audio_profile_html = (
+ f"Bitrate: {audio_profile.bitrate} kbps
"
+ f"Sample Rate: {audio_profile.sample_rate} Hz
"
+ f"Channels: {audio_profile.channels}
"
+ f"Quality Score: {audio_profile.compute_quality_score()}"
+ f""
+ )
+ return Markup(audio_profile_html)
diff --git a/app/jellyfin_routes.py b/app/jellyfin_routes.py
index 1f99f39..90978d0 100644
--- a/app/jellyfin_routes.py
+++ b/app/jellyfin_routes.py
@@ -208,5 +208,5 @@ def search_jellyfin():
if search_query:
results = jellyfin.search_music_tracks(functions._get_token_from_sessioncookie(), search_query)
# Render only the search results section as response
- return render_template('partials/_jf_search_results.html', results=results,spotify_id= spotify_id)
+ return render_template('partials/_jf_search_results.html', results=results,spotify_id= spotify_id,search_query = search_query)
return jsonify({'error': 'No search query provided'}), 400
\ No newline at end of file
diff --git a/config.py b/config.py
index fc0436d..3fc6dae 100644
--- a/config.py
+++ b/config.py
@@ -17,6 +17,7 @@ class Config:
JELLYPLIST_DB_PASSWORD = os.getenv('JELLYPLIST_DB_PASSWORD')
START_DOWNLOAD_AFTER_PLAYLIST_ADD = os.getenv('START_DOWNLOAD_AFTER_PLAYLIST_ADD',"false").lower() == 'true' # If a new Playlist is added, the Download Task will be scheduled immediately
REFRESH_LIBRARIES_AFTER_DOWNLOAD_TASK = os.getenv('REFRESH_LIBRARIES_AFTER_DOWNLOAD_TASK',"false").lower() == 'true'
+ DISPLAY_EXTENDED_AUDIO_DATA = os.getenv('DISPLAY_EXTENDED_AUDIO_DATA',"false").lower() == 'true'
CACHE_TYPE = 'redis'
CACHE_REDIS_PORT = 6379
CACHE_REDIS_HOST = 'redis'
diff --git a/static/css/styles.css b/static/css/styles.css
index 2ef654b..faedd5a 100644
--- a/static/css/styles.css
+++ b/static/css/styles.css
@@ -52,4 +52,9 @@ body {
}
.logo img{
width: 100%;
+}
+@media screen and (min-width: 1600px) {
+ .modal-dialog {
+ max-width: 90%; /* New width for default modal */
+ }
}
\ No newline at end of file
diff --git a/templates/partials/_jf_search_results.html b/templates/partials/_jf_search_results.html
index c50346f..b517eef 100644
--- a/templates/partials/_jf_search_results.html
+++ b/templates/partials/_jf_search_results.html
@@ -7,6 +7,10 @@