renameFromDisk.pl 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. # Includes
  5. use URI::Escape;
  6. use XML::Simple;
  7. use LWP::Simple;
  8. use File::Basename;
  9. use HTTP::Request::Common;
  10. # Parameters
  11. my ($host, $user, $pass, $section, $series) = @ARGV;
  12. if (!$host || !$user || !$pass || (!$section && !$series)) {
  13. die('Usage: ' . basename($0) . ' host[:port] user passwd section_number [series_metadata_number]');
  14. }
  15. # Globals
  16. my $baseURL = 'http://' . $host;
  17. my $auth = 'X-Plex-User=' . $user . '&X-Plex-Pass=' . $pass;
  18. my $xml = new XML::Simple;
  19. my @shows = ();
  20. if ($series) {
  21. # Fetch just the provided show
  22. print STDERR 'Fetching show ' . $series . "\n";
  23. @shows = ('/library/metadata/' . $series . '/children');
  24. } else {
  25. # Fetch the list of all shows in the section
  26. print STDERR "Fetching shows...\n";
  27. {
  28. my $content = get($baseURL . '/library/sections/' . $section . '/all/?' . $auth);
  29. if (!$content) {
  30. die(basename($0) . ': Unable to fetch data for section: ' . $section . "\n");
  31. }
  32. # Find all TV shows in the section
  33. {
  34. my $tree = $xml->XMLin($content);
  35. @shows = keys(%{ $tree->{'Directory'} });
  36. }
  37. }
  38. print STDERR 'Found ' . scalar(@shows) . " shows\n";
  39. }
  40. # Fetch the list of all seasons in each show
  41. my @seasons = ();
  42. print STDERR "Fetching seasons...\n";
  43. foreach my $show (@shows) {
  44. my $content = get($baseURL . $show . '?' . $auth);
  45. if (!$content) {
  46. warn(basename($0) . ': Could not fetch seasons for show: ' . $show . "\n");
  47. next;
  48. }
  49. # Find all seasons in the show
  50. {
  51. my $tree = $xml->XMLin($content);
  52. # XML::Simple retuns different structure depending on the number of same-named child elements
  53. if ($tree->{'Directory'}->{'key'}) {
  54. push(@seasons, $tree->{'Directory'}->{'key'});
  55. } else {
  56. push(@seasons, keys(%{ $tree->{'Directory'} }));
  57. }
  58. }
  59. }
  60. print STDERR 'Found ' . scalar(@seasons) . " seasons\n";
  61. # Forget the list of shows
  62. undef(@shows);
  63. # Fetch the list of all episodes in each season
  64. my @episodes = ();
  65. print STDERR "Fetching episodes...\n";
  66. foreach my $season (@seasons) {
  67. my $content = get($baseURL . $season . '?' . $auth);
  68. if (!$content) {
  69. warn(basename($0) . ': Could not fetch episodes for season: ' . $season . "\n");
  70. next;
  71. }
  72. # Find all episodes in the season
  73. {
  74. my $tree = $xml->XMLin($content);
  75. # XML::Simple retuns different structure depending on the number of same-named child elements
  76. if ($tree->{'Video'}->{'key'}) {
  77. push(@episodes, $tree->{'Video'}->{'key'});
  78. } else {
  79. push(@episodes, keys(%{ $tree->{'Video'} }));
  80. }
  81. }
  82. }
  83. print STDERR 'Found ' . scalar(@episodes) . " episodes\n";
  84. # Forget the list of seasons
  85. undef(@seasons);
  86. # Fetch the metadata from each episode
  87. print STDERR "Checking episodes...\n";
  88. foreach my $episode (@episodes) {
  89. my $content = get($baseURL . $episode . '?' . $auth);
  90. if (!$content) {
  91. warn(basename($0) . ': Could not fetch metadata for episode: ' . $episode . "\n");
  92. next;
  93. }
  94. # Parse the metadata
  95. {
  96. my $tree = $xml->XMLin($content);
  97. my $title = $tree->{'Video'}->{'title'};
  98. my $summary = $tree->{'Video'}->{'summary'};
  99. my $file = $tree->{'Video'}->{'Media'}->{'Part'}->{'file'};
  100. if (!$title) {
  101. warn('Invalid title for episode: ' . $episode . "\n");
  102. next;
  103. }
  104. if (!$file) {
  105. if (!$tree->{'Video'}->{'Media'}->{'key'}) {
  106. warn('No support for multi-file episodes: ' . $episode . "\n");
  107. } else {
  108. warn('Invalid file for episode: ' . $episode . "\n");
  109. }
  110. next;
  111. }
  112. # Rename if the file has a valid name but the episode does not
  113. $file = uri_unescape($file);
  114. $file = basename($file);
  115. $file =~ s/^\d+\s+\-\s*//;
  116. $file =~ s/S\d+E\d+\s+\-\s*//i;
  117. $file =~ s/\.\w{2,3}//;
  118. if (!$file || length($file) < 2 || $file =~ /NoName/i || $file =~ /Episode\s+\d+/i) {
  119. next;
  120. }
  121. if ($file =~ /\s+S\d+D\d+\-\d+$/) {
  122. print STDERR 'Skipping encoder-named file: ' . $file . "\n";
  123. }
  124. if ($title =~ /^Episode\s+\d+$/) {
  125. my $ua = LWP::UserAgent->new();
  126. my $encoded = uri_escape($file);
  127. print STDERR 'Renaming ' . $episode . ' from "' . $title . '" to "' . $file . '"' . "\n";
  128. my $url = $baseURL . $episode . '?title=' . $encoded . '&titleSort=' . $encoded . '&title.locked=1&titleSort.locked=1&' . $auth;
  129. my $request = HTTP::Request->new('PUT' => $url);
  130. $ua->request($request);
  131. if (!$summary) {
  132. print STDERR "\tAlso setting summary\n";
  133. $url = $baseURL . $episode . '?title=' . $encoded . '&summary=' . $encoded . '&summary.locked=1&' . $auth;
  134. $request = HTTP::Request->new('PUT' => $url);
  135. $ua->request($request);
  136. }
  137. }
  138. }
  139. }