Jump to content

Setting collections sort order using API


Recommended Posts

d3v1l1989
Posted (edited)

Hey guys,

I'm having settings collections order using API.
I'd love to sort by year descending ( or even better year descending + sort by name) but no matter what i try emby ends up setting sort order by year ascending showing oldest movies first.
Using descending parrameter gives me unknown param so I'm sure I'm just dumb and can't figure it out.
Can anyone help me out figure out whats wrong?

 

import logging
from typing import List, Dict, Any # Assuming you have requests.Session available as self.session
import requests # For type hinting Session

# Setup logger if not already configured elsewhere
logger = logging.getLogger(__name__)
# Example basic config for testing:
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


class EmbyClient: # Mocking your class structure for testing
    def __init__(self, server_url: str, api_key: str, user_id: str):
        self.server_url = server_url.rstrip('/')
        self.api_key = api_key
        self.user_id = user_id
        self.session = requests.Session()
        self._temp_collections: Dict[str, str] = {} # Example

    def _make_api_request(self, method: str, endpoint_path: str, params: Dict[str, Any] = None, json_data: Dict[str, Any] = None) -> Any:
        """Helper function to make API requests and return JSON data or None."""
        url = f"{self.server_url}{endpoint_path}"
        try:

            if method.upper() == 'GET':
                response = self.session.get(url, params=params, timeout=30)
            elif method.upper() == 'POST':
            
                response = self.session.post(url, params=params, json=json_data, timeout=30)
            else:
                logger.error(f"Unsupported HTTP method: {method}")
                return None

            response.raise_for_status() 
            
            if response.status_code == 204: 
                return {}
            return response.json()
        except requests.exceptions.HTTPError as e:
            logger.error(f"HTTP error during {method} {url}: {e.response.status_code} - {e.response.text[:200]}")
        except requests.exceptions.RequestException as e:
            logger.error(f"Request error during {method} {url}: {e}")
        except ValueError: 
            logger.error(f"Failed to decode JSON response from {method} {url}")
        return None

    def update_collection_items(self, collection_id: str, item_ids: List[str]) -> bool:
        """
        Set the items for a given Emby collection, applying custom sort order.
        The item_ids list should be in the desired final sort order (though this function
        will override with collection-level sorting).
        Args:
            collection_id: The Emby collection ID.
            item_ids: List of Emby item IDs to include in the collection.
        Returns:
            True if successful, False otherwise.
        """
        if not collection_id:
            logger.error("Error: Invalid collection_id")
            return False

        if hasattr(self, '_temp_collections') and collection_id in self._temp_collections:
            collection_name = self._temp_collections[collection_id]
            logger.info(f"Cannot update items for pseudo-collection '{collection_name}'")
            return True # Pretend success

        # 1. Add/Update items in the collection
        unique_item_ids = list(dict.fromkeys(item_ids))

        if len(unique_item_ids) < len(item_ids):
            logger.info(f"Removed {len(item_ids) - len(unique_item_ids)} duplicate item IDs from collection update")

        items_to_set_str = ",".join(unique_item_ids) if unique_item_ids else ""
        add_items_url = f"{self.server_url}/Collections/{collection_id}/Items?api_key={self.api_key}&Ids={items_to_set_str}"

        try:
            logger.info(f"Setting {len(unique_item_ids)} items for collection {collection_id}...")
            response = self.session.post(add_items_url, timeout=30)

            if response.status_code == 204: # 204 No Content is success
                logger.info(f"Successfully set items in collection {collection_id}.")

                # 2. Set and VERIFY the Collection's sort order
                logger.info(f"Attempting to set collection {collection_id} sort to PremiereDate (Descending)...")

                update_response = None
                display_order_update_attempted = False

                # Fetch current collection data. This is important to avoid overwriting other metadata.
                # Using /Items/{Id} endpoint, as /Users/{UserId}/Items/{Id} can sometimes return user-specific views
                # that might not be ideal as a base for updating the item itself.
                # However, for reading initial LockedFields, etc., it should be okay.
                # Sticking with user-specific item endpoint for now as in original code.
                collection_data_url = f"/Users/{self.user_id}/Items/{collection_id}"
                collection_data = self._make_api_request('GET', collection_data_url, params={'api_key': self.api_key})


                if not collection_data:
                    logger.error(f"Failed to fetch existing collection data for ID: {collection_id}. Cannot reliably update sort order.")

                else:
                    collection_metadata_payload = collection_data.copy() # Start with existing data

                    # Define desired sort settings
                    desired_display_order = "PremiereDate"
                    desired_sort_by = "PremiereDate" # Usually aligns with DisplayOrder for clarity
                    desired_sort_order = "Descending"

                    collection_metadata_payload["DisplayOrder"] = desired_display_order
                    collection_metadata_payload["SortBy"] = desired_sort_by
                    collection_metadata_payload["SortOrder"] = desired_sort_order
                    
                    # These are not standard Emby fields for sorting, good to remove if they were experiments
                    collection_metadata_payload.pop("SortByPremiereDate", None)
                    collection_metadata_payload.pop("SortByPremiereFirst", None)

                    # Ensure LockedFields allows DisplayOrder, SortBy, AND SortOrder to be changed
                    locked_fields = collection_metadata_payload.get('LockedFields', [])
                    if not isinstance(locked_fields, list): # Ensure it's a list
                        logger.warning(f"LockedFields for {collection_id} was not a list: {locked_fields}. Resetting to empty list for update.")
                        locked_fields = []
                    
                    fields_to_unlock = ['DisplayOrder', 'SortBy', 'SortOrder']
                    current_locked_fields_set = set(locked_fields)
                    for field in fields_to_unlock:
                        if field in current_locked_fields_set:
                            current_locked_fields_set.remove(field)
                    collection_metadata_payload['LockedFields'] = sorted(list(current_locked_fields_set)) # Keep it sorted for consistency

                    # Send the update to the collection
                    # Use the /Items/{Id} endpoint for updating item metadata
                    collection_item_update_url = f"{self.server_url}/Items/{collection_id}?api_key={self.api_key}"
                    logger.info(f"Collection metadata update payload for {collection_id}: { {k:v for k,v in collection_metadata_payload.items() if k in ['Name', 'DisplayOrder', 'SortBy', 'SortOrder', 'LockedFields']} }") # Log key parts
                    
                    update_item_response = self.session.post(collection_item_update_url, json=collection_metadata_payload, timeout=30)
                    display_order_update_attempted = True # Renaming variable for clarity
                
                # Verification logic
                if display_order_update_attempted:
                    if update_item_response and update_item_response.status_code in [200, 204]:
                        logger.info(f"Attempt to set collection sort to {desired_display_order} ({desired_sort_order}) successful (HTTP {update_item_response.status_code}). Verifying...")
                        
                        # IMMEDIATE VERIFICATION
                        # Use the same user-specific endpoint for verification if that's how user will see it
                        verify_url = f"/Users/{self.user_id}/Items/{collection_id}"
                        verify_params = {
                            'api_key': self.api_key,
                            'Fields': 'DisplayOrder,SortOrder,SortBy,Name,LockedFields' # Request all relevant fields
                        }
                        verify_data = self._make_api_request('GET', verify_url, params=verify_params)

                        if verify_data:
                            actual_display_order = verify_data.get('DisplayOrder', 'NotSet')
                            actual_sort_by = verify_data.get('SortBy', 'NotSet')
                            actual_sort_order = verify_data.get('SortOrder', 'NotSet') # Default to 'NotSet' if key missing
                            collection_name = verify_data.get('Name', 'UnknownName')
                            
                            logger.info(f"VERIFIED: Collection '{collection_name}' (ID: {collection_id}) has DisplayOrder: {actual_display_order}, SortBy: {actual_sort_by}, SortOrder: {actual_sort_order}.")
                            
                            if actual_display_order != desired_display_order or \
                               actual_sort_by != desired_sort_by or \
                               actual_sort_order != desired_sort_order:
                                logger.critical(
                                    f"CRITICAL: Collection sort settings did NOT fully apply for '{collection_name}' (ID: {collection_id})! "
                                    f"Expected DisplayOrder: {desired_display_order}, SortBy: {desired_sort_by}, SortOrder: {desired_sort_order}. "
                                    f"Got DisplayOrder: {actual_display_order}, SortBy: {actual_sort_by}, SortOrder: {actual_sort_order}. "
                                    f"LockedFields after update: {verify_data.get('LockedFields')}"
                                )
                            else:
                                logger.info(f"Successfully verified sort settings for collection '{collection_name}'.")
                        else:
                            logger.warning(f"Could not verify collection sort settings for {collection_id}. Verification GET request failed or returned no data.")
                    elif update_item_response:
                        logger.error(f"FAILED to set collection sort for {collection_id}. Status: {update_item_response.status_code} - {update_item_response.text[:200]}")
                    else: # Should not happen if display_order_update_attempted is true and collection_data was present
                        logger.error(f"FAILED to set collection sort for {collection_id}. No response received from update POST (should not happen if update was attempted).")
                else: # This case means collection_data was not fetched
                    logger.warning(f"Skipped attempting to set collection sort for {collection_id} due to missing initial collection data.")

                # Optional: Trigger a refresh on the collection
                try:
                    refresh_url = f"{self.server_url}/Items/{collection_id}/Refresh?api_key={self.api_key}"
                    refresh_response = self.session.post(refresh_url, timeout=30)
                    if refresh_response.status_code in [200, 204]:
                        logger.info(f"Successfully sent refresh command for collection {collection_id}.")
                    else:
                        logger.warning(f"Failed to send refresh command for collection {collection_id}: {refresh_response.status_code}")
                except Exception as e_refresh:
                    logger.warning(f"Error sending refresh command: {e_refresh}")

                return True # Overall success if items were added, metadata is best-effort
            else:
                logger.error(f"Failed to set items in collection {collection_id}: {response.status_code} - {response.text[:200]}")
                return False
        except Exception as e:
            logger.error(f"Unhandled error updating collection items for {collection_id}: {e}")
            return False

 

Edited by d3v1l1989
d3v1l1989
Posted

So PremiereDate can't use descending parameter at all?
Any idea on how could i work around that so I can sort by year descending because I'm totally lost?

Posted

It should be able to. Doesn’t the detail screen do that?

d3v1l1989
Posted
59 minutes ago, Luke said:

It should be able to. Doesn’t the detail screen do that?

I'm not sure I'm following.
What detail screen?
Are you referring to sorting withing the UI manually?

d3v1l19892
Posted
3 minutes ago, Luke said:

Yes.

I mean yes that works but then I'd have to tell users "hey you can sort movies inside collections too" and explain stuff.

I'd rather just use api to set the collections sorting to at least year descending or ideally have the api support all of the default sorts that are inside the UI.

I would love to see it in the near future because I really want emby to have proper collections and thats why Im working on my own kometa alternative for emby.

 

Amything
Posted

I wanted to be able to see what is new at a glance in my collections. I added custom sort names for items within collections so that they are sorted by Date Added, newest first. Of course that affects the alphabetical sorting everywhere, not the best solution.

d3v1l19892
Posted

Yeah, I also tried that but as you already said it affects entire library so its not a solution.

I feel like emby could be so much better and community could help more if they offered better api options.

Imagine properly sorted collections, custom collection rows on the homepage so you can create for example "Streaming right now" collection row on the homepage where you could at a glance see all the popular platforms and each collection would be sorted based on popularity etc etc.

Ahh..maybe one day

Amything
Posted

Collection rows on home screen with full sorting control is the dream :)

  • Agree 1
hthgihwaymonk
Posted

I agree, being able to sort by desc using the API would be a welcome addition.
at least the android app seems to remember your selection after you've set it in the UI 

  • Agree 1
Posted

The saved sort order in the metadata editor for collections doesn't support all of the same values that can be used manually on the collection screen. So we need to expand on that and then you should be able to do this.

  • Thanks 1
d3v1l19892
Posted

That would honestly be awesome!

  • Thanks 1

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...