Skip to content

Commit

Permalink
Merge pull request #42 from tphoney/movie_playlists
Browse files Browse the repository at this point in the history
(feat) add playlist reading to movies page
  • Loading branch information
tphoney committed Jun 2, 2024
2 parents 0609f5b + 5f5ad3c commit 69babfa
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 103 deletions.
2 changes: 2 additions & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ A tool to compare your plex libraries with other physical media rental / purchas
- [x] amazon via blu-ray.com (customisable region)
- [x] cinema paradiso
- [x] filter by resolution, audio language or new releases
- [x] use playlists to filter what you search for
- [x] TV
- [x] amazon via blu-ray.com (customisable region)
- [x] cinema paradiso
Expand All @@ -28,6 +29,7 @@ A tool to compare your plex libraries with other physical media rental / purchas
- [x] spotify (requires a client id and secret)
- [x] musicbrainz (can use a local copy of the database)
- [x] find new releases, or find similar new artists
- [x] use playlists to filter what you search for
- [x] Runs locally
- [x] no data is stored
- [x] no ads
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func initializeFlags() {

func initializePlexMovies() []types.PlexMovie {
var allMovies []types.PlexMovie
allMovies = append(allMovies, plex.GetPlexMovies(plexIP, plexMovieLibraryID, plexToken, nil)...)
allMovies = append(allMovies, plex.GetPlexMovies(plexIP, plexMovieLibraryID, plexToken)...)

fmt.Printf("\nThere are a total of %d movies in the library.\n\nMovies available:\n", len(allMovies))
return allMovies
Expand Down
173 changes: 133 additions & 40 deletions plex/plex.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,39 +726,109 @@ type MusicPlayList struct {
} `xml:"Track"`
}

type MoviePlaylist struct {
XMLName xml.Name `xml:"MediaContainer"`
Text string `xml:",chardata"`
Size string `xml:"size,attr"`
Composite string `xml:"composite,attr"`
Duration string `xml:"duration,attr"`
LeafCount string `xml:"leafCount,attr"`
PlaylistType string `xml:"playlistType,attr"`
RatingKey string `xml:"ratingKey,attr"`
Smart string `xml:"smart,attr"`
Title string `xml:"title,attr"`
Video []struct {
Text string `xml:",chardata"`
RatingKey string `xml:"ratingKey,attr"`
Key string `xml:"key,attr"`
GUID string `xml:"guid,attr"`
Studio string `xml:"studio,attr"`
Type string `xml:"type,attr"`
Title string `xml:"title,attr"`
LibrarySectionTitle string `xml:"librarySectionTitle,attr"`
LibrarySectionID string `xml:"librarySectionID,attr"`
LibrarySectionKey string `xml:"librarySectionKey,attr"`
ContentRating string `xml:"contentRating,attr"`
Summary string `xml:"summary,attr"`
Rating string `xml:"rating,attr"`
AudienceRating string `xml:"audienceRating,attr"`
ViewCount string `xml:"viewCount,attr"`
LastViewedAt string `xml:"lastViewedAt,attr"`
Year string `xml:"year,attr"`
Tagline string `xml:"tagline,attr"`
Thumb string `xml:"thumb,attr"`
Art string `xml:"art,attr"`
Duration string `xml:"duration,attr"`
OriginallyAvailableAt string `xml:"originallyAvailableAt,attr"`
AddedAt string `xml:"addedAt,attr"`
UpdatedAt string `xml:"updatedAt,attr"`
AudienceRatingImage string `xml:"audienceRatingImage,attr"`
ChapterSource string `xml:"chapterSource,attr"`
PrimaryExtraKey string `xml:"primaryExtraKey,attr"`
RatingImage string `xml:"ratingImage,attr"`
TitleSort string `xml:"titleSort,attr"`
OriginalTitle string `xml:"originalTitle,attr"`
Slug string `xml:"slug,attr"`
SkipCount string `xml:"skipCount,attr"`
Media []struct {
Text string `xml:",chardata"`
ID string `xml:"id,attr"`
Duration string `xml:"duration,attr"`
Bitrate string `xml:"bitrate,attr"`
Width string `xml:"width,attr"`
Height string `xml:"height,attr"`
AspectRatio string `xml:"aspectRatio,attr"`
AudioChannels string `xml:"audioChannels,attr"`
AudioCodec string `xml:"audioCodec,attr"`
VideoCodec string `xml:"videoCodec,attr"`
VideoResolution string `xml:"videoResolution,attr"`
Container string `xml:"container,attr"`
VideoFrameRate string `xml:"videoFrameRate,attr"`
VideoProfile string `xml:"videoProfile,attr"`
AudioProfile string `xml:"audioProfile,attr"`
Part struct {
Text string `xml:",chardata"`
ID string `xml:"id,attr"`
Key string `xml:"key,attr"`
Duration string `xml:"duration,attr"`
File string `xml:"file,attr"`
Size string `xml:"size,attr"`
Container string `xml:"container,attr"`
VideoProfile string `xml:"videoProfile,attr"`
AudioProfile string `xml:"audioProfile,attr"`
} `xml:"Part"`
} `xml:"Media"`
Genre []struct {
Text string `xml:",chardata"`
Tag string `xml:"tag,attr"`
} `xml:"Genre"`
Country []struct {
Text string `xml:",chardata"`
Tag string `xml:"tag,attr"`
} `xml:"Country"`
Director []struct {
Text string `xml:",chardata"`
Tag string `xml:"tag,attr"`
} `xml:"Director"`
Writer []struct {
Text string `xml:",chardata"`
Tag string `xml:"tag,attr"`
} `xml:"Writer"`
Role []struct {
Text string `xml:",chardata"`
Tag string `xml:"tag,attr"`
} `xml:"Role"`
} `xml:"Video"`
}

type Filter struct {
Name string
Value string
Modifier string
}

func GetPlexMovies(ipAddress, libraryID, plexToken string, filters []Filter) (movieList []types.PlexMovie) {
func GetPlexMovies(ipAddress, libraryID, plexToken string) (movieList []types.PlexMovie) {
url := fmt.Sprintf("http://%s:32400/library/sections/%s/all", ipAddress, libraryID)
//nolint:gocritic
// EG
// filter = []plex.Filter{
// // does not have german audio
// {
// Name: "audioLanguage",
// Value: "de",
// Modifier: "\u0021=",
// },
// // has german audio
// {
// Name: "audioLanguage",
// Value: "de",
// Modifier: "=",
// },
// }

for i := range filters {
if i == 0 {
url += "?"
} else {
url += "&"
}
url += fmt.Sprintf("%s%s%s", filters[i].Name, filters[i].Modifier, filters[i].Value)
}

response, err := makePlexAPIRequest(url, plexToken)
if err != nil {
Expand Down Expand Up @@ -837,17 +907,6 @@ func getPlexMovieDetails(ipAddress, plexToken string, movie *types.PlexMovie, ch
ch <- *movie
}

func FilterPlexMovies(movies []types.PlexMovie, filters types.PlexLookupFilters) []types.PlexMovie {
// filter resolutions first
var filteredMovies []types.PlexMovie
for i := range movies {
if slices.Contains(filters.MatchesResolutions, movies[i].Resolution) {
filteredMovies = append(filteredMovies, movies[i])
}
}
return filteredMovies
}

// =================================================================================================
func GetPlexTV(ipAddress, libraryID, plexToken string) (tvShowList []types.PlexTVShow) {
url := fmt.Sprintf("http://%s:32400/library/sections/%s/all", ipAddress, libraryID)
Expand Down Expand Up @@ -1144,18 +1203,33 @@ func GetArtistsFromPlaylist(ipAddress, plexToken, ratingKey string) (playlistIte
url := fmt.Sprintf("http://%s:32400/playlists/%s/items", ipAddress, ratingKey)
response, err := makePlexAPIRequest(url, plexToken)
if err != nil {
fmt.Println("GetPlaylistItems: Error making request:", err)
fmt.Println("GetArtistsFromPlaylist: Error making request:", err)
return playlistItems
}

playlistItems, err = extractArtistsFromPlaylist(response)
if err != nil {
fmt.Println("Error extracting artists from playlist:", err)
}
return playlistItems
}

func GetMoviesFromPlaylist(ipAddress, plexToken, ratingKey string) (playlistItems []types.PlexMovie) {
url := fmt.Sprintf("http://%s:32400/playlists/%s/items", ipAddress, ratingKey)
response, err := makePlexAPIRequest(url, plexToken)
if err != nil {
fmt.Println("GetMoviesromPlaylist: Error making request:", err)
return playlistItems
}

playlistItems, err = extractPlaylistItems(response)
playlistItems, err = extractMoviesFromPlaylist(response)
if err != nil {
fmt.Println("Error extracting playlist items:", err)
}
return playlistItems
}

func extractPlaylistItems(xmlString string) (playlistItems []types.PlexMusicArtist, err error) {
func extractArtistsFromPlaylist(xmlString string) (playlistItems []types.PlexMusicArtist, err error) {
var container MusicPlayList
err = xml.Unmarshal([]byte(xmlString), &container)
if err != nil {
Expand Down Expand Up @@ -1192,6 +1266,25 @@ func extractPlaylistItems(xmlString string) (playlistItems []types.PlexMusicArti
return playlistItems, nil
}

func extractMoviesFromPlaylist(xmlString string) (playlistItems []types.PlexMovie, err error) {
var container MoviePlaylist
err = xml.Unmarshal([]byte(xmlString), &container)
if err != nil {
fmt.Println("Error parsing XML:", err)
return playlistItems, err
}

for i := range container.Video {
playlistItems = append(playlistItems, types.PlexMovie{
Title: container.Video[i].Title,
RatingKey: container.Video[i].RatingKey,
Resolution: container.Video[i].Media[0].VideoResolution,
Year: container.Video[i].Year,
DateAdded: parsePlexDate(container.Video[i].AddedAt)})
}
return playlistItems, nil
}

// =================================================================================================

func makePlexAPIRequest(inputURL, plexToken string) (response string, err error) {
Expand Down
18 changes: 15 additions & 3 deletions plex/plex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestGetPlexMovies(t *testing.T) {
if plexIP == "" || plexMovieLibraryID == "" || plexToken == "" {
t.Skip("ACCEPTANCE TEST: PLEX environment variables not set")
}
result := GetPlexMovies(plexIP, plexMovieLibraryID, plexToken, nil)
result := GetPlexMovies(plexIP, plexMovieLibraryID, plexToken)

if len(result) == 0 {
t.Errorf("Expected at least one TV show, but got %d", len(result))
Expand Down Expand Up @@ -121,7 +121,7 @@ func TestGetPlaylists(t *testing.T) {
if plexIP == "" || plexToken == "" {
t.Skip("ACCEPTANCE TEST: PLEX environment variables not set")
}
playlists, err := GetPlaylists(plexIP, plexToken, "1")
playlists, err := GetPlaylists(plexIP, plexToken, "3")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
Expand All @@ -131,7 +131,7 @@ func TestGetPlaylists(t *testing.T) {
}
}

func TestGetPlaylistItems(t *testing.T) {
func TestGetArtistsFromPlaylist(t *testing.T) {
if plexIP == "" || plexToken == "" {
t.Skip("ACCEPTANCE TEST: PLEX environment variables not set")
}
Expand All @@ -141,6 +141,18 @@ func TestGetPlaylistItems(t *testing.T) {
t.Errorf("Expected at least one item, but got %d", len(items))
}
}

func TestGetMoviesFromPlaylist(t *testing.T) {
if plexIP == "" || plexToken == "" {
t.Skip("ACCEPTANCE TEST: PLEX environment variables not set")
}
items := GetMoviesFromPlaylist(plexIP, plexToken, "111907")
// Check the number of items
if len(items) == 0 {
t.Errorf("Expected at least one item, but got %d", len(items))
}
}

func Test_findLowestResolution(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading

0 comments on commit 69babfa

Please sign in to comment.