1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
1
4
"""
2
5
Description: Create and share playlists based on Most Popular TV/Movies from Tautulli
3
6
and Aired this day in history.
43
46
44
47
Example:
45
48
Use with cron or task to schedule runs
46
-
49
+
47
50
Create Aired Today Playlist from Movies and TV Shows libraries for admin user
48
51
python playlist_manager.py --jbop historyToday --libraries Movies "TV Shows" --action add
49
52
61
64
62
65
Create 10 Most Popular Movies (60 days) Playlist and share to users bob and Black Twin
63
66
python playlist_manager.py --jbop popularMovies --action add --users bob "Black Twin" --days 60 --top 10
64
-
67
+
65
68
Show 5 Most Popular TV Shows (30 days) Playlist
66
69
python playlist_manager.py --jbop popularTv --action show
67
-
70
+
68
71
Show all users current playlists
69
72
python playlist_manager.py --action show --allUsers
70
-
73
+
71
74
Share existing admin Playlists "My Custom Playlist" and "Another Playlist" with all users
72
75
python playlist_manager.py --action share --allUsers --playlists "My Custom Playlist" "Another Playlist"
73
-
76
+
74
77
Search and Filter;
75
-
78
+
76
79
metadata_field_name = title, summary, etc.
77
-
80
+
78
81
--search {metadata_field_name}=value
79
82
search through metadata field for existence of value.
80
-
83
+
81
84
--search {metadata_field_name}=value1,value2,*
82
85
search through metadata field for existence of values.
83
86
*comma separated for AND (value1 AND value2 AND *)
84
-
85
-
86
-
87
+
88
+
89
+
87
90
Excluding;
88
91
89
92
--user becomes excluded if --allUsers is set
106
109
from collections import Counter
107
110
from plexapi .server import PlexServer , CONFIG
108
111
109
- ### EDIT SETTINGS ###
112
+ # # ## EDIT SETTINGS ###
110
113
111
114
PLEX_URL = ''
112
115
PLEX_TOKEN = ''
113
116
TAUTULLI_URL = ''
114
117
TAUTULLI_APIKEY = ''
115
118
116
- ## CODE BELOW ##
119
+ # # # CODE BELOW ##
117
120
118
121
if not PLEX_URL :
119
122
PLEX_URL = CONFIG .data ['auth' ].get ('server_baseurl' )
150
153
today = datetime .datetime .now ().date ()
151
154
weeknum = datetime .date (today .year , today .month , today .day ).isocalendar ()[1 ]
152
155
156
+
153
157
def actions ():
154
158
"""
155
159
add - create new playlist for admin or users
@@ -172,7 +176,7 @@ def selectors():
172
176
'custom' : '{custom} Playlist' ,
173
177
'random' : '{count} Random {libraries} Playlist'
174
178
}
175
-
179
+
176
180
return selections
177
181
178
182
@@ -203,7 +207,7 @@ def exclusions(all_true, select, all_items):
203
207
for x in select :
204
208
all_items .remove (x )
205
209
output = all_items
206
-
210
+
207
211
elif isinstance (all_items , dict ):
208
212
output = {}
209
213
if all_true and not select :
@@ -216,7 +220,7 @@ def exclusions(all_true, select, all_items):
216
220
for key , value in all_items .items ():
217
221
if value not in select :
218
222
output [key ] = value
219
-
223
+
220
224
return output
221
225
222
226
@@ -226,7 +230,7 @@ def get_home_stats(time_range, stats_count):
226
230
'cmd' : 'get_home_stats' ,
227
231
'time_range' : time_range ,
228
232
'stats_count' : stats_count ,
229
- 'stats_type' : 0 } # stats_type = plays
233
+ 'stats_type' : 0 } # stats_type = plays
230
234
231
235
try :
232
236
r = requests .get (TAUTULLI_URL .rstrip ('/' ) + '/api/v2' , params = payload )
@@ -269,14 +273,14 @@ def sort_by_dates(video, date_type):
269
273
return [[video .ratingKey ] + [str (video .originallyAvailableAt )]]
270
274
271
275
# todo-me return object
272
- except Exception as e :
276
+ except Exception :
273
277
# print(e)
274
278
return
275
279
276
280
277
281
def multi_filter_search (keyword_dict , library , search_eps = None ):
278
282
"""Allowing for multiple filter or search values
279
-
283
+
280
284
Parameters
281
285
----------
282
286
keyword_dict: dict
@@ -318,17 +322,18 @@ def multi_filter_search(keyword_dict, library, search_eps=None):
318
322
for episode in show .episodes (** {key : values }):
319
323
ep_lst += [episode .ratingKey ]
320
324
multi_lst += ep_lst
321
-
325
+
322
326
else :
323
327
multi_lst += [item .ratingKey for item in library .all (** {key : values })]
324
328
counts = Counter (multi_lst )
325
329
# Use amount of keywords to check that all keywords were found in results
326
330
search_lst = [id for id in multi_lst if counts [id ] >= keyword_count ]
327
-
331
+
328
332
return list (set (search_lst ))
329
333
334
+
330
335
def get_content (libraries , jbop , filters = None , search = None , limit = None ):
331
- """Get all movies or episodes from LIBRARY_NAME
336
+ """Get all movies or episodes from LIBRARY_NAME.
332
337
333
338
Parameters
334
339
----------
@@ -342,6 +347,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
342
347
list
343
348
Sorted list of Movie and episodes that
344
349
aired on today's date.
350
+
345
351
"""
346
352
child_lst = []
347
353
filter_lst = []
@@ -378,7 +384,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
378
384
child_lst += filter_lst
379
385
if keywords and filters :
380
386
child_lst += list (set (filter_lst ) & set (search_lst ))
381
-
387
+
382
388
elif library_type == 'show' :
383
389
# Decisions to stack filter and search
384
390
if keywords :
@@ -402,17 +408,17 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
402
408
for episode in show .episodes ():
403
409
filter_lst += [episode .ratingKey ]
404
410
child_lst += filter_lst
405
-
411
+
406
412
if keywords and filters :
407
413
child_lst += list (set (filter_lst ) & set (search_lst ))
408
414
else :
409
415
pass
410
416
# Keep only results found from both search and filters
411
417
if keywords and filters :
412
418
child_lst = list (set (i for i in child_lst if child_lst .count (i ) > 1 ))
413
-
419
+
414
420
play_lst = child_lst
415
-
421
+
416
422
else :
417
423
for library in libraries .keys ():
418
424
plex_library = plex .library .sectionByID (library )
@@ -440,7 +446,7 @@ def get_content(libraries, jbop, filters=None, search=None, limit=None):
440
446
# Sort by original air date, oldest first
441
447
# todo-me move sorting and add more sorting options
442
448
aired_lst = sorted (child_lst , key = operator .itemgetter (1 ))
443
-
449
+
444
450
# Remove date used for sorting
445
451
play_lst = [x [0 ] for x in aired_lst ]
446
452
else :
@@ -474,7 +480,7 @@ def build_playlist(jbop, libraries=None, days=None, top=None, filters=None, sear
474
480
for stat in home_stats :
475
481
if stat ['stat_id' ] in ['popular_tv' , 'popular_movies' ]:
476
482
keys_list += [x ['rating_key' ] for x in stat ['rows' ] if
477
- str (x ['section_id' ]) in libraries .keys ()]
483
+ str (x ['section_id' ]) in libraries .keys ()]
478
484
else :
479
485
try :
480
486
keys_list = get_content (libraries , jbop , filters , search , limit )
@@ -525,11 +531,12 @@ def show_playlist(playlist_title, playlist_keys):
525
531
title = unicodedata .normalize ('NFKD' , title ).encode ('ascii' , 'ignore' ).translate (None , "'" )
526
532
playlist_list .append (title )
527
533
528
- print (u"Contents of Playlist {title}:\n {playlist}" .format (title = playlist_title ,
529
- playlist = ', ' .join (playlist_list )))
534
+ print (u"Contents of Playlist {title}:\n {playlist}" .format (
535
+ title = playlist_title ,
536
+ playlist = ', ' .join (playlist_list )))
530
537
exit ()
531
-
532
-
538
+
539
+
533
540
def create_playlist (playlist_title , playlist_keys , server , user ):
534
541
"""
535
542
Parameters
@@ -552,13 +559,13 @@ def create_playlist(playlist_title, playlist_keys, server, user):
552
559
playlist_list .append (episode )
553
560
else :
554
561
playlist_list .append (plex_obj )
555
- except Exception as e :
562
+ except Exception :
556
563
try :
557
564
obj = plex .fetchItem (key )
558
565
print ("{} may not have permission to this title: {}" .format (user , obj .title ))
559
566
# print("Error: {}".format(e))
560
567
pass
561
- except Exception as e :
568
+ except Exception :
562
569
print ('Rating Key: {}, may have been deleted or moved.' .format (key ))
563
570
# print("Error: {}".format(e))
564
571
@@ -578,7 +585,7 @@ def delete_playlist(playlist_dict, title):
578
585
"""
579
586
server = playlist_dict ['server' ]
580
587
user = playlist_dict ['user' ]
581
-
588
+
582
589
try :
583
590
# todo-me this needs improvement
584
591
for playlist in server .playlists ():
@@ -595,14 +602,14 @@ def delete_playlist(playlist_dict, title):
595
602
print ("...Deleted Playlist: {playlist.title} for '{user}'."
596
603
.format (playlist = playlist , user = user ))
597
604
598
- except :
605
+ except Exception :
599
606
# print("Playlist not found on '{user}' account".format(user=user))
600
607
pass
601
608
602
609
603
610
def create_title (jbop , libraries , days , filters , search , limit ):
604
611
"""
605
-
612
+
606
613
Parameters
607
614
----------
608
615
jbop: str
@@ -672,9 +679,9 @@ def create_title(jbop, libraries, days, filters, search, limit):
672
679
673
680
674
681
if __name__ == "__main__" :
675
-
676
- parser = argparse . ArgumentParser ( description = "Create, share, and clean Playlists for users." ,
677
- formatter_class = argparse .RawTextHelpFormatter )
682
+ parser = argparse . ArgumentParser (
683
+ description = "Create, share, and clean Playlists for users." ,
684
+ formatter_class = argparse .RawTextHelpFormatter )
678
685
# todo-me use parser grouping instead of choices for action and jbop?
679
686
parser .add_argument ('--jbop' , choices = selectors ().keys (), metavar = '' ,
680
687
help = 'Playlist selector.\n '
@@ -715,7 +722,7 @@ def create_title(jbop, libraries, days, filters, search, limit):
715
722
parser .add_argument ('--search' , action = 'append' , type = lambda kv : kv .split ("=" ),
716
723
help = 'Search non-filtered metadata fields for keywords '
717
724
'in title, summary, etc.' )
718
-
725
+
719
726
opts = parser .parse_args ()
720
727
721
728
title = ''
@@ -739,7 +746,7 @@ def create_title(jbop, libraries, days, filters, search, limit):
739
746
# If filter key used more than once than consider filtering values with OR statement
740
747
if filter_count > 1 :
741
748
filters_lst = []
742
-
749
+
743
750
filters = dict (opts .filter )
744
751
for k , v in filters .items ():
745
752
# If comma separated filter then consider filtering values with AND statement
@@ -756,10 +763,10 @@ def create_title(jbop, libraries, days, filters, search, limit):
756
763
757
764
# Defining libraries
758
765
libraries = exclusions (opts .allLibraries , opts .libraries , sections_dict )
759
-
766
+
760
767
# Defining selected playlists
761
768
selected_playlists = exclusions (opts .allPlaylists , opts .playlists , playlist_lst )
762
-
769
+
763
770
# Create user server objects
764
771
if users :
765
772
for user in users :
@@ -776,12 +783,13 @@ def create_title(jbop, libraries, days, filters, search, limit):
776
783
'user' : user ,
777
784
'user_selected' : user_selected ,
778
785
'all_playlists' : all_playlists })
779
-
786
+
780
787
if opts .self or not users :
781
- playlist_dict ['data' ].append ({'server' : plex ,
782
- 'user' : 'admin' ,
783
- 'user_selected' : selected_playlists ,
784
- 'all_playlists' : playlist_lst })
788
+ playlist_dict ['data' ].append ({
789
+ 'server' : plex ,
790
+ 'user' : 'admin' ,
791
+ 'user_selected' : selected_playlists ,
792
+ 'all_playlists' : playlist_lst })
785
793
786
794
if not opts .jbop and opts .action == 'show' :
787
795
print ("Displaying the user's playlist(s)..." )
@@ -790,7 +798,7 @@ def create_title(jbop, libraries, days, filters, search, limit):
790
798
playlists = data ['all_playlists' ]
791
799
print ("{}'s current playlist(s): {}" .format (user , ', ' .join (playlists )))
792
800
exit ()
793
-
801
+
794
802
if libraries :
795
803
title = create_title (opts .jbop , libraries , opts .days , filters , search , opts .limit )
796
804
keys_list = build_playlist (opts .jbop , libraries , opts .days , opts .top , filters , search , opts .limit )
@@ -801,18 +809,18 @@ def create_title(jbop, libraries, days, filters, search, limit):
801
809
for data in playlist_dict ['data' ]:
802
810
titles = data ['user_selected' ]
803
811
delete_playlist (data , titles )
804
-
812
+
805
813
# Check if limit exist and if it's greater than the pulled list of rating keys
806
814
if opts .limit and len (keys_list ) > int (opts .limit ):
807
815
if opts .jbop == 'random' :
808
816
keys_list = random .sample ((keys_list ), opts .limit )
809
817
else :
810
818
keys_list = keys_list [:opts .limit ]
811
-
819
+
812
820
# Setting custom name if provided
813
821
if opts .name :
814
822
title = opts .name
815
-
823
+
816
824
if opts .jbop and opts .action == 'show' :
817
825
show_playlist (title , keys_list )
818
826
@@ -823,7 +831,7 @@ def create_title(jbop, libraries, days, filters, search, limit):
823
831
print ('Creating playlist(s)...' )
824
832
for data in playlist_dict ['data' ]:
825
833
create_playlist (title , keys_list , data ['server' ], data ['user' ])
826
-
834
+
827
835
if opts .action == 'add' :
828
836
print ('Creating playlist(s)...' )
829
837
for data in playlist_dict ['data' ]:
0 commit comments