1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-09 15:01:39 -04:00

Compare commits

...

4 Commits

Author SHA1 Message Date
Leonardo Galli
0b278c7db8 Searching for movie now works with downloading. They also get imported fine.
Additionally, a whole series (or movie in this case) can now be
downloaded manually.
Note: It probably won't start downloading missed releases. Only manually
clicking search for is working ATM.
2016-12-28 17:13:18 +01:00
Leonardo Galli
0b765d10fe Updated some text to say movies instead of series 2016-12-27 18:48:59 +01:00
Leonardo Galli
20dbdfb344 Added first iteration of adding movies.
Currently working:
- Searching for new Movies on IMDb (very hacky)
- Adding movie as a series with one season and episode (very hacky)
- Rarbg.to indexer. (somewhat hacky)

TODO:
- Tweak release specifications so that they do not cause exceptions.
- Add Movie struct so that searching for ones is not so hacky.
- rework movies UI.
2016-12-27 18:31:38 +01:00
Leonardo Galli
426448ed98 Update readme.md 2016-12-25 12:43:05 +01:00
22 changed files with 188 additions and 49 deletions

View File

@@ -1,7 +1,7 @@
# Sonarr #
Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
This fork of Sonarr aims to turn it into something like Couchpotato.
At the moment almost nothing is implemented.
## Major Features Include: ##

View File

@@ -80,7 +80,9 @@ namespace NzbDrone.Core.DecisionEngine
if (remoteEpisode.Series == null)
{
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown Series"));
//remoteEpisode.DownloadAllowed = true; //Fuck you :)
//decision = GetDecisionForReport(remoteEpisode, searchCriteria);
decision = new DownloadDecision(remoteEpisode, new Rejection("Unknown release. Movie not Found."));
}
else if (remoteEpisode.Episodes.Empty())
{
@@ -143,8 +145,9 @@ namespace NzbDrone.Core.DecisionEngine
{
e.Data.Add("report", remoteEpisode.Release.ToJson());
e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title);
return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));
_logger.Error(e, "Couldn't evaluate decision on " + remoteEpisode.Release.Title + ", with spec: " + spec.GetType().Name);
//return new Rejection(string.Format("{0}: {1}", spec.GetType().Name, e.Message));//TODO UPDATE SPECS!
//return null;
}
return null;

View File

@@ -30,7 +30,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (subject.Episodes.Any(e => !e.AirDateUtc.HasValue || e.AirDateUtc.Value.After(DateTime.UtcNow)))
{
_logger.Debug("Full season release {0} rejected. All episodes haven't aired yet.", subject.Release.Title);
return Decision.Reject("Full season release rejected. All episodes haven't aired yet.");
//return Decision.Reject("Full season release rejected. All episodes haven't aired yet.");
}
}

View File

@@ -28,7 +28,8 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
{
_logger.Debug("Season number does not match searched season number, skipping.");
return Decision.Reject("Wrong season");
//return Decision.Reject("Wrong season");
//Unnecessary for Movies
}
return Decision.Accept();

View File

@@ -29,19 +29,19 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
{
_logger.Debug("Season number does not match searched season number, skipping.");
return Decision.Reject("Wrong season");
//return Decision.Reject("Wrong season");
}
if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Any())
{
_logger.Debug("Full season result during single episode search, skipping.");
return Decision.Reject("Full season pack");
//return Decision.Reject("Full season pack");
}
if (!remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers.Contains(singleEpisodeSpec.EpisodeNumber))
{
_logger.Debug("Episode number does not match searched episode number, skipping.");
return Decision.Reject("Wrong episode");
//return Decision.Reject("Wrong episode");
}
return Decision.Accept();

View File

@@ -88,12 +88,13 @@ namespace NzbDrone.Core.Indexers.Rarbg
if (tvdbId.HasValue)
{
requestBuilder.AddQueryParam("search_tvdb", tvdbId.Value);
string imdbId = string.Format("tt{0:D7}", tvdbId);
requestBuilder.AddQueryParam("search_imdb", imdbId);
}
if (query.IsNotNullOrWhiteSpace())
{
requestBuilder.AddQueryParam("search_string", string.Format(query, args));
//requestBuilder.AddQueryParam("search_string", string.Format(query, args));
}
if (!Settings.RankedOnly)
@@ -101,7 +102,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
requestBuilder.AddQueryParam("ranked", "0");
}
requestBuilder.AddQueryParam("category", "18;41");
requestBuilder.AddQueryParam("category", "movies");
requestBuilder.AddQueryParam("limit", "100");
requestBuilder.AddQueryParam("token", _tokenProvider.GetToken(Settings));
requestBuilder.AddQueryParam("format", "json_extended");

View File

@@ -17,8 +17,8 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
if (localEpisode.ParsedEpisodeInfo.FullSeason)
{
_logger.Debug("Single episode file detected as containing all episodes in the season");
return Decision.Reject("Single episode file contains all episodes in seasons");
//_logger.Debug("Single episode file detected as containing all episodes in the season"); //Not needed for Movies mwhahahahah
//return Decision.Reject("Single episode file contains all episodes in seasons");
}
return Decision.Accept();

View File

@@ -10,6 +10,7 @@ using NzbDrone.Core.Exceptions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource.SkyHook.Resource;
using NzbDrone.Core.Tv;
using Newtonsoft.Json;
namespace NzbDrone.Core.MetadataSource.SkyHook
{
@@ -37,7 +38,11 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
var httpResponse = _httpClient.Get<ShowResource>(httpRequest);
string imdbId = string.Format("tt{0:D7}", tvdbSeriesId);
var imdbRequest = new HttpRequest("http://www.omdbapi.com/?i="+ imdbId + "&plot=full&r=json");
var httpResponse = _httpClient.Get(imdbRequest);
if (httpResponse.HasHttpError)
{
@@ -51,8 +56,47 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
}
}
var episodes = httpResponse.Resource.Episodes.Select(MapEpisode);
var series = MapSeries(httpResponse.Resource);
var response = httpResponse.Content;
dynamic json = JsonConvert.DeserializeObject(response);
var series = new Series();
series.Title = json.Title;
series.TitleSlug = series.Title.ToLower().Replace(" ", "-");
series.Overview = json.Plot;
series.CleanTitle = Parser.Parser.CleanSeriesTitle(series.Title);
series.TvdbId = tvdbSeriesId;
string airDateStr = json.Released;
DateTime airDate = DateTime.Parse(airDateStr);
series.FirstAired = airDate;
series.Year = airDate.Year;
series.ImdbId = imdbId;
series.Images = new List<MediaCover.MediaCover>();
string url = json.Poster;
var imdbPoster = new MediaCover.MediaCover(MediaCoverTypes.Poster, url);
series.Images.Add(imdbPoster);
string runtime = json.Runtime;
int runtimeNum = 0;
int.TryParse(runtime.Replace("min", "").Trim(), out runtimeNum);
series.Runtime = runtimeNum;
var season = new Season();
season.SeasonNumber = 1;
season.Monitored = true;
series.Seasons.Add(season);
var episode = new Episode();
episode.AirDate = airDate.ToBestDateString();
episode.Title = json.Title;
episode.SeasonNumber = 1;
episode.EpisodeNumber = 1;
episode.Overview = series.Overview;
episode.AirDate = airDate.ToShortDateString();
var episodes = new List<Episode> { episode };
return new Tuple<Series, List<Episode>>(series, episodes.ToList());
}
@@ -89,6 +133,66 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
.AddQueryParam("term", title.ToLower().Trim())
.Build();
var searchTerm = lowerTitle.Replace("+", "_").Replace(" ", "_");
var firstChar = searchTerm.First();
var imdbRequest = new HttpRequest("https://v2.sg.media-imdb.com/suggests/"+firstChar+"/" + searchTerm + ".json");
var response = _httpClient.Get(imdbRequest);
var imdbCallback = "imdb$" + searchTerm + "(";
var responseCleaned = response.Content.Replace(imdbCallback, "").TrimEnd(")");
dynamic json = JsonConvert.DeserializeObject(responseCleaned);
var imdbMovies = new List<Series>();
foreach (dynamic entry in json.d)
{
var imdbMovie = new Series();
imdbMovie.ImdbId = entry.id;
string noTT = imdbMovie.ImdbId.Replace("tt", "");
try
{
imdbMovie.TvdbId = (int)Double.Parse(noTT);
}
catch
{
imdbMovie.TvdbId = 0;
}
try
{
imdbMovie.SortTitle = entry.l;
imdbMovie.Title = entry.l;
string titleSlug = entry.l;
imdbMovie.TitleSlug = titleSlug.ToLower().Replace(" ", "-");
imdbMovie.Year = entry.y;
imdbMovie.Images = new List<MediaCover.MediaCover>();
try
{
string url = entry.i[0];
var imdbPoster = new MediaCover.MediaCover(MediaCoverTypes.Poster, url);
imdbMovie.Images.Add(imdbPoster);
}
catch (Exception e)
{
_logger.Debug(entry);
continue;
}
imdbMovies.Add(imdbMovie);
}
catch
{
}
}
return imdbMovies;
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest);
return httpResponse.Resource.SelectList(MapSeries);

View File

@@ -26,6 +26,10 @@ namespace NzbDrone.Core.Parser
new Regex(@"^(?:\W*S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,3}(?!\d+)))+){2,}",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Matches Movie name with AirYear
new Regex(@"^(?<title>.+?)?(?:(?:[-_\W](?<![()\[!]))*(?<year>(?<!e|x)\d{4}(?!p|i|\d+|\)|\]|\W\d+)))+(\W+|_|$)(?!\\)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
//Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc)
new Regex(@"^(?:S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex]|_){1,2}(?<episode>\d{2,3}(?!\d+)))+)",
RegexOptions.IgnoreCase | RegexOptions.Compiled),
@@ -296,6 +300,8 @@ namespace NzbDrone.Core.Parser
public static ParsedEpisodeInfo ParseTitle(string title)
{
ParsedEpisodeInfo realResult = null;
try
{
if (!ValidateBeforeParsing(title)) return null;
@@ -342,6 +348,8 @@ namespace NzbDrone.Core.Parser
}
}
foreach (var regex in ReportTitleRegex)
{
var match = regex.Matches(simpleTitle);
@@ -383,6 +391,8 @@ namespace NzbDrone.Core.Parser
Logger.Debug("Release Hash parsed: {0}", result.ReleaseHash);
}
realResult = result;
return result;
}
}
@@ -401,7 +411,7 @@ namespace NzbDrone.Core.Parser
}
Logger.Debug("Unable to parse {0}", title);
return null;
return realResult;
}
public static string ParseSeriesName(string title)
@@ -525,6 +535,7 @@ namespace NzbDrone.Core.Parser
int airYear;
int.TryParse(matchCollection[0].Groups["airyear"].Value, out airYear);
//int.TryParse(matchCollection[0].Groups["year"].Value, out airYear);
ParsedEpisodeInfo result;

View File

@@ -100,7 +100,7 @@ namespace NzbDrone.Core.Parser
if (parsedEpisodeInfo == null)
{
return _seriesService.FindByTitle(title);
return _seriesService.FindByTitle(title); //Here we have a problem since it is not possible for movies to find a scene mapping, so these releases are always rejected :(
}
var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
@@ -252,10 +252,12 @@ namespace NzbDrone.Core.Parser
{
Series series = null;
/*var localEpisode = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
var sceneMappingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle);
if (sceneMappingTvdbId.HasValue)
if (localEpisode != null)
{
if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMappingTvdbId.Value)
if (searchCriteria != null && searchCriteria.Series.TvdbId == localEpisode.TvdbId)
{
return searchCriteria.Series;
}
@@ -269,7 +271,7 @@ namespace NzbDrone.Core.Parser
}
return series;
}
}*/ //This is only to find scene mapping should not be necessary for movies.
if (searchCriteria != null)
{

View File

@@ -114,7 +114,7 @@ namespace NzbDrone.Core.Qualities
new QualityDefinition(Quality.WEBDL720p) { Weight = 8, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.Bluray720p) { Weight = 9, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.WEBDL1080p) { Weight = 10, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.Bluray1080p) { Weight = 11, MinSize = 0, MaxSize = 100 },
new QualityDefinition(Quality.Bluray1080p) { Weight = 11, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.HDTV2160p) { Weight = 12, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.WEBDL2160p) { Weight = 13, MinSize = 0, MaxSize = null },
new QualityDefinition(Quality.Bluray2160p) { Weight = 14, MinSize = 0, MaxSize = null },

View File

@@ -5,7 +5,7 @@
<i class="icon-sonarr-hdd"/>
Import existing series on disk
</button>
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-sonarr-active hidden-xs"></i> Add New Series</button>
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-sonarr-active hidden-xs"></i> Add New Movie</button>
</div>
</div>
</div>
@@ -14,4 +14,3 @@
<div id="add-series-workspace"></div>
</div>
</div>

View File

@@ -11,7 +11,7 @@
{{#if folder}}
<input type="text" class="form-control x-series-search" value="{{folder.name}}">
{{else}}
<input type="text" class="form-control x-series-search" placeholder="Start typing the name of series you want to add ...">
<input type="text" class="form-control x-series-search" placeholder="Start typing the name of the movie you want to add ...">
{{/if}}
</div>
</div>

View File

@@ -35,10 +35,11 @@ module.exports = NzbDroneCell.extend({
},
_manualSearch : function() {
console.warn(this.cellValue);
vent.trigger(vent.Commands.ShowEpisodeDetails, {
episode : this.cellValue,
hideSeriesLink : true,
openingTab : 'search'
});
}
});
});

View File

@@ -13,7 +13,7 @@ var SeriesEditorLayout = require('./Series/Editor/SeriesEditorLayout');
module.exports = NzbDroneController.extend({
addSeries : function(action) {
this.setTitle('Add Series');
this.setTitle('Add Movie');
this.showMainRegion(new AddSeriesLayout({ action : action }));
},
@@ -56,4 +56,4 @@ module.exports = NzbDroneController.extend({
this.setTitle('Series Editor');
this.showMainRegion(new SeriesEditorLayout());
}
});
});

View File

@@ -11,7 +11,7 @@ Handlebars.registerHelper('poster', function() {
if (!poster[0].url.match(/^https?:\/\//)) {
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, poster[0].url, 250)));
} else {
var url = poster[0].url.replace(/^https?\:/, '');
var url = poster[0].url.replace(/^https?\:/, 'https://'); //IMDb posters need https to work, k?
return new Handlebars.SafeString('<img class="series-poster x-series-poster" {0}>'.format(Handlebars.helpers.defaultImg.call(null, url)));
}
}
@@ -28,7 +28,7 @@ Handlebars.registerHelper('imdbUrl', function() {
});
Handlebars.registerHelper('tvdbUrl', function() {
return 'http://www.thetvdb.com/?tab=series&id=' + this.tvdbId;
return 'http://imdb.com/title/tt' + this.tvdbId;
});
Handlebars.registerHelper('tvRageUrl', function() {

View File

@@ -19,7 +19,7 @@
</div>
<div class="navbar-collapse collapse x-navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="{{UrlBase}}/" class="x-series-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-series"></i> Series</a></li>
<li><a href="{{UrlBase}}/" class="x-series-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-series"></i> Movies</a></li>
<li><a href="{{UrlBase}}/calendar" class="x-calendar-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-calendar"></i> Calendar</a></li>
<li><a href="{{UrlBase}}/activity" class="x-activity-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-activity"></i> Activity<span id="x-queue-count" class="navbar-info"></span></a></li>
<li><a href="{{UrlBase}}/wanted" class="x-wanted-nav"><i class="icon-sonarr-navbar-icon icon-sonarr-navbar-wanted"></i> Wanted</a></li>
@@ -37,8 +37,8 @@
<div class="col-md-6 col-md-offset-3">
<div class="input-group">
<span class="input-group-addon"><i class="fa fa-search"></i></span>
<input type="text" class="col-md-6 form-control x-series-search" placeholder="Search the series in your library">
<input type="text" class="col-md-6 form-control x-series-search" placeholder="Search the movies in your library">
</div>
</div>
</div>
</div>
</div>

View File

@@ -29,9 +29,9 @@
</div>
<div class="col-md-3">
<span class="series-info-links">
<a href="{{traktUrl}}" class="label label-info">Trakt</a>
<!--<a href="{{traktUrl}}" class="label label-info">Trakt</a>
<a href="{{tvdbUrl}}" class="label label-info">The TVDB</a>
<a href="{{tvdbUrl}}" class="label label-info">The TVDB</a>-->
{{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>

View File

@@ -32,7 +32,8 @@ module.exports = Marionette.Layout.extend({
refresh : '.x-refresh',
rename : '.x-rename',
search : '.x-search',
poster : '.x-series-poster'
poster : '.x-series-poster',
manualSearch : '.x-manual-search'
},
events : {
@@ -41,7 +42,8 @@ module.exports = Marionette.Layout.extend({
'click .x-edit' : '_editSeries',
'click .x-refresh' : '_refreshSeries',
'click .x-rename' : '_renameSeries',
'click .x-search' : '_seriesSearch'
'click .x-search' : '_seriesSearch',
'click .x-manual-search' : '_manualSearchM'
},
initialize : function() {
@@ -178,11 +180,11 @@ module.exports = Marionette.Layout.extend({
if (self.model.get('id') !== seriesId) {
return [];
}
if (sceneSeasonNumber === undefined) {
sceneSeasonNumber = seasonNumber;
}
return _.where(self.model.get('alternateTitles'),
function(alt) {
return alt.sceneSeasonNumber === sceneSeasonNumber || alt.seasonNumber === seasonNumber;
@@ -254,5 +256,17 @@ module.exports = Marionette.Layout.extend({
} else {
$('body').removeClass('backdrop');
}
},
_manualSearchM : function() {
console.warn("Manual Search started");
console.warn(this.model.get("seriesId"));
console.warn(this.model)
console.warn(this.episodeCollection);
vent.trigger(vent.Commands.ShowEpisodeDetails, {
episode : this.episodeCollection.models[0],
hideSeriesLink : true,
openingTab : 'search'
});
}
});
});

View File

@@ -5,23 +5,26 @@
<div class="col-md-12 col-lg-10">
<div>
<h1 class="header-text">
<i class="x-monitored" title="Toggle monitored state for entire series"/>
<i class="x-monitored" title="Toggle monitored state for movie"/>
{{title}}
<div class="series-actions pull-right">
<div class="x-episode-file-editor">
<i class="icon-sonarr-episode-file" title="Modify episode files for series"/>
<i class="icon-sonarr-episode-file" title="Modify episode files for movie"/>
</div>
<div class="x-refresh">
<i class="icon-sonarr-refresh icon-can-spin" title="Update series info and scan disk"/>
<i class="icon-sonarr-refresh icon-can-spin" title="Update movie info and scan disk"/>
</div>
<div class="x-rename">
<i class="icon-sonarr-rename" title="Preview rename for all episodes"/>
</div>
<div class="x-search">
<i class="icon-sonarr-search" title="Search for monitored episodes in this series"/>
<i class="icon-sonarr-search" title="Search for movie"/>
</div>
<div class="x-manual-search">
<i class="icon-sonarr-search-manual" title="Manual Search"/>
</div>
<div class="x-edit">
<i class="icon-sonarr-edit" title="Edit series"/>
<i class="icon-sonarr-edit" title="Edit movie"/>
</div>
</div>
</h1>

View File

@@ -9,7 +9,7 @@
<div class="col-md-4 col-md-offset-4">
<a href="/addseries" class='btn btn-lg btn-block btn-success x-add-series'>
<i class='icon-sonarr-add'></i>
Add Series
Add Movie
</a>
</div>
</div>

View File

@@ -80,7 +80,7 @@ module.exports = Marionette.Layout.extend({
collapse : true,
items : [
{
title : 'Add Series',
title : 'Add Movie',
icon : 'icon-sonarr-add',
route : 'addseries'
},