Initial commit
This commit is contained in:
3
jellyfin/.dockerignore
Normal file
3
jellyfin/.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
__pycache__/
|
||||
Dockerifile
|
||||
*.http
|
||||
9
jellyfin/Dockerfile
Normal file
9
jellyfin/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM python:3.10
|
||||
|
||||
RUN pip install pydantic requests python-dotenv
|
||||
|
||||
COPY jellyfin /app/jellyfin
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENTRYPOINT [ "python" ]
|
||||
85
jellyfin/create_all_songs_playlist.py
Normal file
85
jellyfin/create_all_songs_playlist.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Set your Jellyfin server address and API key here
|
||||
server_address = "https://jellyfin.alexmickelson.guru"
|
||||
api_key = os.environ["JELLYFIN_TOKEN"]
|
||||
|
||||
# Set the API endpoints to get all songs and create a playlist
|
||||
songs_endpoint = (
|
||||
"/Users/b30951b36b37400498dbfd182d49a42e/Items"
|
||||
+ "?SortBy=DateCreated,SortName"
|
||||
+ "&SortOrder=Descending"
|
||||
+ "&IncludeItemTypes=Audio"
|
||||
+ "&Recursive=true"
|
||||
+ "&Fields=AudioInfo,ParentId"
|
||||
+ "&StartIndex=0"
|
||||
+ "&ImageTypeLimit=1"
|
||||
+ "&EnableImageTypes=Primary"
|
||||
+ "&Limit=100"
|
||||
+ "&ParentId=7e64e319657a9516ec78490da03edccb"
|
||||
)
|
||||
songs_endpoint = "/Users/b30951b36b37400498dbfd182d49a42e/Items"
|
||||
|
||||
# Set the parameters for the API request to get all songs
|
||||
params = {
|
||||
"api_key": api_key,
|
||||
"SortBy": "SortName",
|
||||
"ParentId": "7e64e319657a9516ec78490da03edccb",
|
||||
}
|
||||
|
||||
# Make the API request to get all songs
|
||||
response = requests.get(server_address + songs_endpoint, params=params)
|
||||
# Parse the JSON response
|
||||
data = json.loads(response.text)
|
||||
# # Loop through the songs and print their names
|
||||
for song in data["Items"]:
|
||||
print(song["Name"], song["Id"])
|
||||
|
||||
|
||||
# Create a list of all song IDs
|
||||
song_ids = [song["Id"] for song in data["Items"]]
|
||||
ids = ",".join(song_ids)
|
||||
# print(ids)
|
||||
playlist_data = {
|
||||
"Name": "All Songs",
|
||||
"UserId": "b30951b36b37400498dbfd182d49a42e",
|
||||
"Ids": ids,
|
||||
"MediaType": "Audio",
|
||||
}
|
||||
headers = {"Content-type": "application/json"}
|
||||
params = {"api_key": api_key}
|
||||
playlist_endpoint = "/Playlists"
|
||||
# https://jellyfin.alexmickelson.guru/Playlists?Name=test playlist&Ids=f78ddd409c5ebb2405f5477d15e8e23c&userId=b30951b36b37400498dbfd182d49a42e
|
||||
response = requests.post(
|
||||
server_address + playlist_endpoint,
|
||||
headers=headers,
|
||||
params=params,
|
||||
data=json.dumps(playlist_data),
|
||||
)
|
||||
# print(response.text)
|
||||
playlist_id = response.json()["Id"]
|
||||
|
||||
|
||||
|
||||
|
||||
# add_song_url = f"/Playlists/{playlist_id}/Items"
|
||||
# params = {"api_key": api_key}
|
||||
# body = {
|
||||
# "Ids": ids,
|
||||
# "UserId": "b30951b36b37400498dbfd182d49a42e",
|
||||
# "MediaType": "Audio",
|
||||
# }
|
||||
|
||||
# response = requests.post(
|
||||
# server_address + add_song_url, headers=headers, params=params, json=body
|
||||
# )
|
||||
# print(response.text)
|
||||
# print(response.status_code)
|
||||
# print(response.headers)
|
||||
|
||||
# jellyfin_service.logout()
|
||||
50
jellyfin/jellyfin.http
Normal file
50
jellyfin/jellyfin.http
Normal file
@@ -0,0 +1,50 @@
|
||||
# https://jellyfin.alexmickelson.guru/api-docs/swagger/index.html
|
||||
# https://gist.github.com/nielsvanvelzen/ea047d9028f676185832e51ffaf12a6f
|
||||
GET https://jellyfin.alexmickelson.guru/Users/b30951b36b37400498dbfd182d49a42e/Items
|
||||
?SortBy=SortName&SortOrder=Ascending
|
||||
&IncludeItemTypes=Playlist
|
||||
&Recursive=true
|
||||
&Fields=PrimaryImageAspectRatio,SortName,CanDelete
|
||||
&StartIndex=0
|
||||
&api_key={{$dotenv JELLYFIN_TOKEN}}
|
||||
|
||||
|
||||
###
|
||||
GET https://jellyfin.alexmickelson.guru/Users/b30951b36b37400498dbfd182d49a42e/Items
|
||||
?IncludeItemTypes=Playlist
|
||||
&Recursive=true
|
||||
&ParentId=7e64e319657a9516ec78490da03edccb
|
||||
&api_key={{$dotenv JELLYFIN_TOKEN}}
|
||||
|
||||
###
|
||||
# get items from unindexed playlist
|
||||
GET https://jellyfin.alexmickelson.guru/Playlists/2f191b23f0a49e70d6f90e9d82e408c6/Items
|
||||
?Fields=PrimaryImageAspectRatio
|
||||
&EnableImageTypes=Primary,Backdrop,Banner,Thumb
|
||||
&UserId=b30951b36b37400498dbfd182d49a42e
|
||||
&api_key={{$dotenv JELLYFIN_TOKEN}}
|
||||
|
||||
### remove item from unindexed
|
||||
DELETE https://jellyfin.alexmickelson.guru/Playlists/2f191b23f0a49e70d6f90e9d82e408c6/Items
|
||||
?EntryIds=186f4d63492b405b97865ff9a99ef3ab
|
||||
&userId=b30951b36b37400498dbfd182d49a42e
|
||||
Authorization: MediaBrowser Client="scriptclient", Device="script", DeviceId="asdfasdfasdfasdfasdf", Version="1.0.0", Token="f313e2045fc34ce3ac510ce9ba2be1fc"
|
||||
|
||||
### get all playlists
|
||||
GET https://jellyfin.alexmickelson.guru/Users/b30951b36b37400498dbfd182d49a42e/Items
|
||||
?api_key={{$dotenv JELLYFIN_TOKEN}}
|
||||
&ParentId=29772619d609592f4cdb3fc34a6ec97d
|
||||
|
||||
### get token by user/pass
|
||||
POST https://jellyfin.alexmickelson.guru/Users/AuthenticateByName
|
||||
Content-Type: application/json
|
||||
Authorization: MediaBrowser Client="scriptclient", Device="script", DeviceId="asdfasdfasdfasdfasdf", Version="1.0.0", Token=""
|
||||
|
||||
{
|
||||
"Username": "alex",
|
||||
"Pw": "{{$dotenv JELLYFIN_PASSWORD}}"
|
||||
}
|
||||
|
||||
###
|
||||
POST https://jellyfin.alexmickelson.guru/Sessions/Logout
|
||||
Authorization: MediaBrowser Client="scriptclient", Device="script", DeviceId="asdfasdfasdfasdfasdf", Version="1.0.0", Token="c704c71900cc41d2a454a4f3b5132778"
|
||||
163
jellyfin/jellyfin_service.py
Normal file
163
jellyfin/jellyfin_service.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from functools import lru_cache
|
||||
import os
|
||||
from pprint import pprint
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
server_address = "https://jellyfin.alexmickelson.guru"
|
||||
# api_key = os.environ["JELLYFIN_TOKEN"]
|
||||
username = os.environ["JELLYFIN_USER"]
|
||||
password = os.environ["JELLYFIN_PASSWORD"]
|
||||
alex_user_id = "b30951b36b37400498dbfd182d49a42e"
|
||||
all_songs_playlist_id = "2e176c02e7cc7f460c40bb1510723510"
|
||||
unindexed_playlist_id = "2f191b23f0a49e70d6f90e9d82e408c6"
|
||||
|
||||
|
||||
class Song(BaseModel):
|
||||
Id: str
|
||||
Name: str
|
||||
Album: Optional[str] = Field(default=None)
|
||||
Artists: Optional[List[str]] = Field(default=None)
|
||||
|
||||
|
||||
class PlaylistSong(BaseModel):
|
||||
Id: str
|
||||
PlaylistItemId: str
|
||||
Name: str
|
||||
Album: Optional[str] = Field(default=None)
|
||||
Artists: Optional[List[str]] = Field(default=None)
|
||||
|
||||
|
||||
class Playlist(BaseModel):
|
||||
Id: str
|
||||
Name: str
|
||||
Songs: List[PlaylistSong]
|
||||
|
||||
|
||||
@lru_cache(maxsize=10)
|
||||
def get_token():
|
||||
auth_endpoint = f"{server_address}/Users/AuthenticateByName"
|
||||
body = {"Username": username, "Pw": password}
|
||||
response = requests.post(
|
||||
auth_endpoint,
|
||||
json=body,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": 'MediaBrowser Client="scriptclient", Device="script", DeviceId="testscriptasdfasdfasdf", Version="1.0.0", Token=""',
|
||||
},
|
||||
)
|
||||
return response.json()["AccessToken"]
|
||||
|
||||
|
||||
def get_auth_headers():
|
||||
token = get_token()
|
||||
return {
|
||||
"Authorization": f'MediaBrowser Client="scriptclient", Device="script", DeviceId="asdfasdfasdfasdfasdf", Version="1.0.0", Token="{token}"'
|
||||
}
|
||||
|
||||
|
||||
def get_all_songs():
|
||||
songs_endpoint = (
|
||||
f"{server_address}/Users/{alex_user_id}/Items"
|
||||
# + "?SortBy=DateCreated,SortName"
|
||||
# + "&SortOrder=Descending"
|
||||
+ "?IncludeItemTypes=Audio"
|
||||
+ "&Recursive=true"
|
||||
# + "&Fields=AudioInfo,ParentId"
|
||||
# + "&StartIndex=0"
|
||||
# + "&ImageTypeLimit=1"
|
||||
# + "&EnableImageTypes=Primary"
|
||||
# + "&Limit=100"
|
||||
+ "&ParentId=7e64e319657a9516ec78490da03edccb"
|
||||
)
|
||||
params = {
|
||||
"SortBy": "SortName",
|
||||
}
|
||||
response = requests.get(songs_endpoint, params=params, headers=get_auth_headers())
|
||||
if not response.ok:
|
||||
print(response.status_code)
|
||||
print(response.text)
|
||||
data = response.json()
|
||||
|
||||
songs = [Song(**song) for song in data["Items"]]
|
||||
return songs
|
||||
|
||||
|
||||
def add_song_to_playlist(song_id: str, playlist_id: str):
|
||||
add_song_endpoint = f"{server_address}/Playlists/{playlist_id}/Items"
|
||||
params = {"ids": song_id, "userId": alex_user_id}
|
||||
response = requests.post(
|
||||
add_song_endpoint, params=params, headers=get_auth_headers()
|
||||
)
|
||||
if not response.ok:
|
||||
print(response.status_code)
|
||||
print(response.text)
|
||||
|
||||
|
||||
def remove_song_from_playlist(song_playlist_id: str, playlist_id: str):
|
||||
url = f"{server_address}/Playlists/{playlist_id}/Items"
|
||||
params = {
|
||||
"EntryIds": song_playlist_id,
|
||||
"userId": alex_user_id,
|
||||
} # , "apiKey": api_key}
|
||||
response = requests.delete(url, params=params, headers=get_auth_headers())
|
||||
if not response.ok:
|
||||
print(response.status_code)
|
||||
print(response.text)
|
||||
print(response.url)
|
||||
print(song_playlist_id)
|
||||
print(playlist_id)
|
||||
print(response.content)
|
||||
pprint(response.request.headers)
|
||||
|
||||
|
||||
def get_songs_in_playlist(playlist_id: str):
|
||||
url = f"{server_address}/Playlists/{playlist_id}/Items"
|
||||
params = {"userId": alex_user_id}
|
||||
response = requests.get(url, params=params, headers=get_auth_headers())
|
||||
if not response.ok:
|
||||
print(response.status_code)
|
||||
print(response.text)
|
||||
raise Exception(f"Error getting songs in playlist: {playlist_id}")
|
||||
data = response.json()
|
||||
|
||||
songs = [PlaylistSong.parse_obj(song) for song in data["Items"]]
|
||||
return songs
|
||||
|
||||
|
||||
def get_all_playlists():
|
||||
url = f"{server_address}/Users/{alex_user_id}/Items"
|
||||
params = {
|
||||
"IncludeItemTypes": "Playlist",
|
||||
"Recursive": True,
|
||||
"ParentId": "29772619d609592f4cdb3fc34a6ec97d",
|
||||
}
|
||||
response = requests.get(url, params=params, headers=get_auth_headers())
|
||||
if not response.ok:
|
||||
print(response.status_code)
|
||||
print(response.text)
|
||||
raise Exception("Error getting all playlists")
|
||||
|
||||
data = response.json()
|
||||
print("got all playlists", len(data["Items"]))
|
||||
playlists: List[Playlist] = []
|
||||
for playlist in data["Items"]:
|
||||
songs = get_songs_in_playlist(playlist["Id"])
|
||||
playlist_object = Playlist(
|
||||
Id=playlist["Id"], Name=playlist["Name"], Songs=songs
|
||||
)
|
||||
playlists.append(playlist_object)
|
||||
|
||||
return playlists
|
||||
|
||||
|
||||
def logout():
|
||||
url = f"{server_address}/Sessions/Logout"
|
||||
response = requests.post(url, headers=get_auth_headers())
|
||||
print("ending session: " + str(response.status_code))
|
||||
20
jellyfin/update_all_songs_playlist.py
Normal file
20
jellyfin/update_all_songs_playlist.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from jellyfin import jellyfin_service
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
all_songs = jellyfin_service.get_all_songs()
|
||||
print("total songs", len(all_songs))
|
||||
playlist_songs = jellyfin_service.get_songs_in_playlist(
|
||||
jellyfin_service.all_songs_playlist_id
|
||||
)
|
||||
print("songs already in playlist", len(playlist_songs))
|
||||
playlist_ids = [s.Id for s in playlist_songs]
|
||||
|
||||
for song in all_songs:
|
||||
if song.Id not in playlist_ids:
|
||||
print(f"adding song {song.Name} to playlist")
|
||||
jellyfin_service.add_song_to_playlist(
|
||||
song.Id, jellyfin_service.all_songs_playlist_id
|
||||
)
|
||||
|
||||
jellyfin_service.logout()
|
||||
34
jellyfin/update_unindexed.py
Normal file
34
jellyfin/update_unindexed.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from pprint import pprint
|
||||
from jellyfin import jellyfin_service
|
||||
|
||||
if __name__ == "__main__":
|
||||
all_songs = jellyfin_service.get_all_songs()
|
||||
playlists = jellyfin_service.get_all_playlists()
|
||||
song_ids_in_playlist = list(
|
||||
set(
|
||||
song.Id
|
||||
for playlist in playlists
|
||||
for song in playlist.Songs
|
||||
if playlist.Id != jellyfin_service.unindexed_playlist_id
|
||||
and playlist.Id != jellyfin_service.all_songs_playlist_id
|
||||
)
|
||||
)
|
||||
unindexed_playlist = next(
|
||||
p for p in playlists if p.Id == jellyfin_service.unindexed_playlist_id
|
||||
)
|
||||
unindexed_songs_ids = [song.Id for song in unindexed_playlist.Songs]
|
||||
for song in all_songs:
|
||||
if song.Id not in song_ids_in_playlist:
|
||||
if song.Id not in unindexed_songs_ids:
|
||||
print(f"adding {song.Name} to unindexed playlist")
|
||||
jellyfin_service.add_song_to_playlist(
|
||||
song.Id, jellyfin_service.unindexed_playlist_id
|
||||
)
|
||||
for song in unindexed_playlist.Songs:
|
||||
if song.Id in song_ids_in_playlist:
|
||||
print(f"removing {song.Name} from unindexed playlist")
|
||||
# pprint(song)
|
||||
jellyfin_service.remove_song_from_playlist(
|
||||
song.PlaylistItemId, jellyfin_service.unindexed_playlist_id
|
||||
)
|
||||
jellyfin_service.logout()
|
||||
Reference in New Issue
Block a user