mirror of
https://github.com/moparisthebest/SickRage
synced 2025-01-05 10:58:01 -05:00
Added anime support with anidb support.
Added fanzub anime nzb provider. Fixed NyaaTorrents anime provider. This is in testing phase so bugs are to be expected.
This commit is contained in:
parent
3b6534ca1d
commit
9a3e7ab0a9
BIN
gui/slick/images/providers/anidb.gif
Normal file
BIN
gui/slick/images/providers/anidb.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
BIN
gui/slick/images/providers/fanzub.gif
Normal file
BIN
gui/slick/images/providers/fanzub.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
gui/slick/images/providers/nyaatorrents.png
Normal file
BIN
gui/slick/images/providers/nyaatorrents.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
103
gui/slick/interfaces/default/config_anime.tmpl
Normal file
103
gui/slick/interfaces/default/config_anime.tmpl
Normal file
@ -0,0 +1,103 @@
|
||||
#import sickbeard
|
||||
#set global $title="Config - Anime"
|
||||
#set global $header="Anime"
|
||||
|
||||
#set global $sbPath="../.."
|
||||
|
||||
#set global $topmenu="config"#
|
||||
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl")
|
||||
|
||||
#if $varExists('header')
|
||||
<h1 class="header">$header</h1>
|
||||
#else
|
||||
<h1 class="title">$title</h1>
|
||||
#end if
|
||||
|
||||
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script>
|
||||
|
||||
<div id="config">
|
||||
<div id="config-content">
|
||||
|
||||
<form id="configForm" action="saveAnime" method="post">
|
||||
|
||||
<div id="config-components">
|
||||
|
||||
<div id="core-component-group1" class="component-group clearfix">
|
||||
<div class="component-group-desc">
|
||||
<h3><a href="http://anidb.info" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/providers/anidb.gif" alt="AniDB" title="AniDB" width="16" height="16" /> AniDB</a></h3>
|
||||
<p>AniDB is non-profit database of anime information that is freely open to the public</p>
|
||||
</div>
|
||||
|
||||
<fieldset class="component-group-list">
|
||||
<div class="field-pair">
|
||||
<input type="checkbox" class="enabler" name="use_anidb" id="use_anidb" #if $sickbeard.USE_ANIDB then "checked=\"checked\"" else ""# />
|
||||
<label class="clearfix" for="use_notifo">
|
||||
<span class="component-title">Enable</span>
|
||||
<span class="component-desc">Should Sick Beard use data from AniDB?</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="content_use_anidb">
|
||||
<div class="field-pair">
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title">AniDB Username</span>
|
||||
<input type="text" name="anidb_username" id="anidb_username" value="$sickbeard.ANIDB_USERNAME" size="35" />
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">Username of your AniDB account</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title">AniDB Password</span>
|
||||
<input type="password" name="anidb_password" id="anidb_password" value="$sickbeard.ANIDB_PASSWORD" size="35" />
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">Password of your AniDB account</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field-pair">
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title">AniDB MyList</span>
|
||||
<input type="checkbox" name="anidb_use_mylist" id="anidb_use_mylist" #if $sickbeard.ANIDB_USE_MYLIST then "checked=\"checked\"" else ""# />
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">Do you want to add the PostProcessed Episodes to the MyList ?</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="submit" class="config_submitter" value="Save Changes" />
|
||||
</fieldset>
|
||||
|
||||
</div><!-- /component-group //-->
|
||||
<div id="core-component-group2" class="component-group clearfix">
|
||||
|
||||
<div class="component-group-desc">
|
||||
<h3>Look and Feel</h3>
|
||||
</div>
|
||||
<fieldset class="component-group-list">
|
||||
<div class="field-pair">
|
||||
<input type="checkbox" class="enabler" name="split_home" id="split_home" #if $sickbeard.ANIME_SPLIT_HOME then "checked=\"checked\"" else ""# />
|
||||
<label class="clearfix" for="use_notifo">
|
||||
<span class="component-title">Split show lists</span>
|
||||
<span class="component-desc">Separate anime and normal shows in groups</span>
|
||||
</label>
|
||||
</div>
|
||||
<input type="submit" class="config_submitter" value="Save Changes" />
|
||||
</fieldset>
|
||||
</div><!-- /component-group //-->
|
||||
<br/><input type="submit" class="config_submitter" value="Save Changes" /><br/>
|
||||
|
||||
</div><!-- /config-components //-->
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
</div></div>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_bottom.tmpl")
|
@ -467,6 +467,42 @@
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<input type="radio" name="naming_anime" id="naming_anime" value="1" #if $sickbeard.NAMING_ANIME == 1then "checked=\"checked\"" else ""#/>
|
||||
<label class="clearfix" for="naming_anime">
|
||||
<span class="component-title">Add Absolute Number</span>
|
||||
<span class="component-desc">Add the absolute number to the season/episode format?</span>
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">Only applies to animes. (eg. S15E45 - 310 vs S15E45)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<input type="radio" name="naming_anime" id="naming_anime_only" value="2" #if $sickbeard.NAMING_ANIME == 2 then "checked=\"checked\"" else ""#/>
|
||||
<label class="clearfix" for="naming_anime_only">
|
||||
<span class="component-title">Only Absolute Number</span>
|
||||
<span class="component-desc">Replace season/episode format with absolute number</span>
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">Only applies to animes.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<input type="radio" name="naming_anime" id="naming_anime_none" value="3" #if $sickbeard.NAMING_ANIME == 3 then "checked=\"checked\"" else ""#/>
|
||||
<label class="clearfix" for="naming_anime_none">
|
||||
<span class="component-title">No Absolute Number</span>
|
||||
<span class="component-desc">Dont include the absolute number</span>
|
||||
</label>
|
||||
<label class="nocheck clearfix">
|
||||
<span class="component-title"> </span>
|
||||
<span class="component-desc">Only applies to animes.</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field-pair">
|
||||
<input type="checkbox" id="naming_strip_year" name="naming_strip_year" #if $sickbeard.NAMING_STRIP_YEAR then "checked=\"checked\"" else ""#/>
|
||||
<label class="clearfix" for="naming_strip_year">
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
<script type="text/javascript" src="$sbRoot/js/displayShow.js?$sbPID"></script>
|
||||
<script type="text/javascript" src="$sbRoot/js/plotTooltip.js?$sbPID"></script>
|
||||
<script type="text/javascript" src="$sbRoot/js/sceneExceptionsTooltip.js"></script>
|
||||
<script type="text/javascript" src="$sbRoot/js/ajaxEpSearch.js?$sbPID"></script>
|
||||
<script type="text/javascript" src="$sbRoot/js/ajaxEpSubtitles.js?$sbPID"></script>
|
||||
<script type="text/javascript" src="$sbRoot/js/ajaxEpRetry.js?$sbPID"></script>
|
||||
@ -31,8 +32,19 @@
|
||||
<div class="align-left"><b>Change Show:</b>
|
||||
<div class="navShow"><img id="prevShow" src="$sbRoot/images/prev.gif" alt="<<" title="Prev Show" /></div>
|
||||
<select id="pickShow">
|
||||
#for $curShow in $sortedShowList:
|
||||
<option value="$curShow.indexerid" #if $curShow == $show then "selected=\"selected\"" else ""#>$curShow.name</option>
|
||||
#for $curShowList in $sortedShowLists:
|
||||
#set $curShowType = $curShowList[0]
|
||||
#set $curShowList = $curShowList[1]
|
||||
|
||||
#if len($sortedShowLists) > 1:
|
||||
<optgroup label="$curShowType">
|
||||
#end if
|
||||
#for $curShow in $curShowList:
|
||||
<option value="$curShow.indexerid" #if $curShow == $show then "selected=\"selected\"" else ""#>$curShow.name</option>
|
||||
#end for
|
||||
#if len($sortedShowLists) > 1:
|
||||
</optgroup>
|
||||
#end if
|
||||
#end for
|
||||
</select>
|
||||
<div class="navShow"><img id="nextShow" src="$sbRoot/images/next.gif" alt=">>" title="Next Show" /></div>
|
||||
@ -158,7 +170,8 @@
|
||||
<tr><td class="showLegend">Flat Folders: </td><td><img src="$sbRoot/images/#if $show.flatten_folders == 1 or $sickbeard.NAMING_FORCE_FOLDERS then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
<tr><td class="showLegend">Paused: </td><td><img src="$sbRoot/images/#if int($show.paused) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
<tr><td class="showLegend">Air-by-Date: </td><td><img src="$sbRoot/images/#if int($show.air_by_date) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
<tr><td class="showLegend">Sports: </td><td><img src="$sbRoot/images/#if int($show.sports) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
<tr><td class="showLegend">Sports: </td><td><img src="$sbRoot/images/#if int($show.is_sports) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
<tr><td class="showLegend">Anime: </td><td><img src="$sbRoot/images/#if int($show.is_anime) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
<tr><td class="showLegend">DVD Order: </td><td><img src="$sbRoot/images/#if int($show.dvdorder) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
#if $anyQualities + $bestQualities
|
||||
<tr><td class="showLegend">Archive First Match: </td><td><img src="$sbRoot/images/#if int($show.archive_firstmatch) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr>
|
||||
@ -206,20 +219,36 @@
|
||||
<br />
|
||||
|
||||
<table class="sickbeardTable" cellspacing="1" border="0" cellpadding="0">
|
||||
|
||||
|
||||
#for $epResult in $sqlResults:
|
||||
#if not $sickbeard.DISPLAY_SHOW_SPECIALS and int($epResult["season"]) == 0:
|
||||
#continue
|
||||
#end if
|
||||
|
||||
#if (epResult["season"], epResult["episode"]) in $xem_numbering:
|
||||
#set ($dfltSeas, $dfltEpis, $dfltAbsolute) = $xem_numbering[(epResult["season"], epResult["episode"])]
|
||||
#elif $xem_numbering and (epResult["season"], epResult["episode"]) not in $xem_numbering:
|
||||
#set ($dfltSeas, $dfltEpis, $dfltAbsolute) = (0,0,0)
|
||||
#else:
|
||||
#set ($dfltSeas, $dfltEpis, $dfltAbsolute) = (epResult["season"], epResult["episode"], epResult["absolute_number"])
|
||||
#end if
|
||||
|
||||
#if (epResult["season"], epResult["episode"]) in $scene_numbering:
|
||||
#set ($scSeas, $scEpis, $scAbsolute) = $scene_numbering[(epResult["season"], epResult["episode"])]
|
||||
#set $dfltEpNumbering = False
|
||||
#else
|
||||
#set ($scSeas, $scEpis, $scAbsolute) = ($dfltSeas, $dfltEpis, $dfltAbsolute)
|
||||
#set $dfltEpNumbering = True
|
||||
#end if
|
||||
|
||||
#if int($epResult["season"]) != $curSeason:
|
||||
<tr><td colspan="10" style="height: 0px; padding:0; margin:0;"><a name="season-$epResult["season"]"></a></td></tr>
|
||||
<tr><td colspan="11" style="height: 0px; padding:0; margin:0;"><a name="season-$epResult["season"]"></a></td></tr>
|
||||
<tr class="seasonheader" id="season-$epResult["season"]" >
|
||||
<td colspan="10">
|
||||
<td colspan="11">
|
||||
<h2>#if int($epResult["season"]) == 0 then "Specials" else "Season "+str($epResult["season"])#</h2>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="season-$epResult["season"]-cols"><th width="1%"><input type="checkbox" class="seasonCheck" id="$epResult["season"]" /></th><th>NFO</th><th>TBN</th><th>Episode</th><th>Scene #</th><th>Name</th><th class="nowrap">Airdate</th><th>Filename</th>#if $sickbeard.USE_SUBTITLES and $show.subtitles then "<th>Subtitles</th>" else ""#<th>Status</th><th>Search</th></tr>
|
||||
<tr id="season-$epResult["season"]-cols"><th width="1%"><input type="checkbox" class="seasonCheck" id="$epResult["season"]" /></th><th>NFO</th><th>TBN</th><th>Episode</th><th class="nowrap">Scene #</th><th class="nowrap">Absolute #</th><th>Name</th><th class="nowrap">Airdate</th><th>Filename</th>#if $sickbeard.USE_SUBTITLES and $show.subtitles then "<th>Subtitles</th>" else ""#<th>Status</th><th>Search</th></tr>
|
||||
#set $curSeason = int($epResult["season"])
|
||||
#end if
|
||||
|
||||
@ -235,34 +264,37 @@
|
||||
<td align="center"><img src="$sbRoot/images/#if $epResult["hastbn"] == 1 then "tbn.gif\" alt=\"Y" else "tbn-no.gif\" alt=\"N"#" width="23" height="11" /></td>
|
||||
<td align="center">$epResult["episode"]</td>
|
||||
<td align="center">
|
||||
#if int($show.air_by_date) != 1 and int($show.sports) != 1
|
||||
#if (epResult["season"], epResult["episode"]) in $xem_numbering:
|
||||
#set ($dfltSeas, $dfltEpis) = $xem_numbering[(epResult["season"], epResult["episode"])]
|
||||
#elif $xem_numbering and (epResult["season"], epResult["episode"]) not in $xem_numbering:
|
||||
#set ($dfltSeas, $dfltEpis) = (0,0)
|
||||
#else:
|
||||
#set ($dfltSeas, $dfltEpis) = (epResult["season"], epResult["episode"])
|
||||
#end if
|
||||
#if (epResult["season"], epResult["episode"]) in $scene_numbering:
|
||||
#set ($scSeas, $scEpis) = $scene_numbering[(epResult["season"], epResult["episode"])]
|
||||
#set $dfltEpNumbering = False
|
||||
#else
|
||||
#set ($scSeas, $scEpis) = ($dfltSeas, $dfltEpis)
|
||||
#set $dfltEpNumbering = True
|
||||
#end if
|
||||
<input type="text" placeholder="<%=str(dfltSeas) + 'x' + str(dfltEpis)%>" size="6" maxlength="8"
|
||||
class="sceneSeasonXEpisode" data-for-season="$epResult["season"]" data-for-episode="$epResult["episode"]"
|
||||
id="sceneSeasonXEpisode_$show.indexerid<%="_"+str(epResult["season"])+"_"+str(epResult["episode"])%>"
|
||||
title="Change the value here if scene numbering differs from the indexer episode numbering"
|
||||
#if $dfltEpNumbering:
|
||||
value=""
|
||||
#else
|
||||
value="<%=str(scSeas) + 'x' + str(scEpis)%>"
|
||||
#end if
|
||||
style="padding: 0; text-align: center; max-width: 60px;"
|
||||
/>
|
||||
#if not $show.air_by_date and not $show.is_sports
|
||||
<input type="text" placeholder="<%=str(dfltSeas) + 'x' + str(dfltEpis)%>" size="6" maxlength="8"
|
||||
class="sceneSeasonXEpisode" data-for-season="$epResult["season"]" data-for-episode="$epResult["episode"]"
|
||||
id="sceneSeasonXEpisode_$show.indexerid<%="_"+str(epResult["season"])+"_"+str(epResult["episode"])%>"
|
||||
title="Change the value here if scene numbering differs from the indexer episode numbering"
|
||||
#if $dfltEpNumbering:
|
||||
value=""
|
||||
#else
|
||||
value="<%=str(scSeas) + 'x' + str(scEpis)%>"
|
||||
#end if
|
||||
style="padding: 0; text-align: center; max-width: 60px;"
|
||||
/>
|
||||
#else
|
||||
N/A
|
||||
-
|
||||
#end if
|
||||
</td>
|
||||
<td align="center">
|
||||
#if not $show.air_by_date and not $show.is_sports
|
||||
<input type="text" placeholder="<%=str(dfltAbsolute)%>" size="6" maxlength="8"
|
||||
class="sceneAbsolute" data-for-season="$epResult["season"]" data-for-episode="$epResult["episode"]"
|
||||
id="sceneAbsolute_$show.indexerid<%="_"+str(epResult["season"])+"_"+str(epResult["episode"])%>"
|
||||
title="Change the value here if scene absolute numbering differs from the indexer absolute numbering"
|
||||
#if $dfltEpNumbering:
|
||||
value=""
|
||||
#else
|
||||
value="<%=str(scAbsolute)%>"
|
||||
#end if
|
||||
style="padding: 0; text-align: center; max-width: 60px;"
|
||||
/>
|
||||
#else
|
||||
-
|
||||
#end if
|
||||
</td>
|
||||
<td class="title">
|
||||
|
@ -120,6 +120,10 @@ This <b>DOES NOT</b> allow SickRage to download non-english TV episodes!<br />
|
||||
<input type="checkbox" name="sports" #if $show.sports == 1 then "checked=\"checked\"" else ""# /><br />
|
||||
(check this if the show is a sporting or MMA event)
|
||||
<br /><br />
|
||||
<b>Anime: </b>
|
||||
<input type="checkbox" name="anime" #if $show.is_anime then "CHECKED" else ""#><br />
|
||||
(check this if the show is released as Show.265 rather than Show.S02E03, this show is an anime)
|
||||
<br /><br />
|
||||
<b>DVD Order: </b>
|
||||
<input type="checkbox" name="dvdorder" #if $show.dvdorder == 1 then "checked=\"checked\"" else ""# /><br/>
|
||||
(check this if you wish to use the DVD order instead of the Airing order)
|
||||
|
@ -33,6 +33,14 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field-pair alt">
|
||||
<input type="checkbox" name="anime" id="anime" #if $sickbeard.ANIME_DEFAULT then "checked=\"checked\"" else ""# />
|
||||
<label for="anime" class="clearfix">
|
||||
<span class="component-title">Anime</span>
|
||||
<span class="component-desc">Is this show an Anime?</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
#set $qualities = $Quality.splitQuality($sickbeard.QUALITY_DEFAULT)
|
||||
#set global $anyQualities = $qualities[0]
|
||||
#set global $bestQualities = $qualities[1]
|
||||
|
@ -272,6 +272,7 @@ a > i.icon-question-sign { background-image: url("$sbRoot/images/glyphicons-half
|
||||
<li><a href="$sbRoot/config/subtitles/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Subtitles Settings</a></li>
|
||||
<li><a href="$sbRoot/config/postProcessing/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Post Processing</a></li>
|
||||
<li><a href="$sbRoot/config/notifications/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Notifications</a></li>
|
||||
<li><a href="$sbRoot/config/anime/"><img src="$sbRoot/images/menu/config16.png" alt="" width="16" height="16" />Anime</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="donate"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YCTA5TEN2JE2J" rel="noreferrer" onclick="window.open('${sickbeard.ANON_REDIRECT}' + this.href); return false;"><img src="$sbRoot/images/paypal/btn_donate_LG.gif" alt="[donate]" /></a></li>
|
||||
|
@ -220,7 +220,7 @@ $(document).ready(function () {
|
||||
$('#naming_custom_sports').change(function () {
|
||||
setup_sports_naming();
|
||||
});
|
||||
|
||||
|
||||
$('#naming_multi_ep').change(fill_examples);
|
||||
$('#naming_pattern').focusout(fill_examples);
|
||||
$('#naming_pattern').keyup(function () {
|
||||
|
39
gui/slick/js/sceneExceptionsTooltip.js
Normal file
39
gui/slick/js/sceneExceptionsTooltip.js
Normal file
@ -0,0 +1,39 @@
|
||||
$(function () {
|
||||
$('.showTitle a').each(function () {
|
||||
match = $(this).parent().attr("id").match(/^scene_exception_(\d+)$/);
|
||||
$(this).qtip({
|
||||
content: {
|
||||
text: 'Loading...',
|
||||
ajax: {
|
||||
url: $("#sbRoot").val() + '/home/sceneExceptions',
|
||||
type: 'GET',
|
||||
data: {
|
||||
show: match[1]
|
||||
},
|
||||
success: function (data, status) {
|
||||
this.set('content.text', data);
|
||||
}
|
||||
}
|
||||
},
|
||||
show: {
|
||||
solo: true
|
||||
},
|
||||
position: {
|
||||
viewport: $(window),
|
||||
my: 'top center',
|
||||
at: 'bottom center',
|
||||
adjust: {
|
||||
y: 3,
|
||||
x: 0
|
||||
}
|
||||
},
|
||||
style: {
|
||||
tip: {
|
||||
corner: true,
|
||||
method: 'polygon'
|
||||
},
|
||||
classes: 'ui-tooltip-rounded ui-tooltip-shadow ui-tooltip-sb'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
796
lib/adba/__init__.py
Normal file
796
lib/adba/__init__.py
Normal file
@ -0,0 +1,796 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
import threading
|
||||
from time import time, sleep, strftime, localtime
|
||||
from types import *
|
||||
|
||||
from aniDBlink import AniDBLink
|
||||
from aniDBcommands import *
|
||||
from aniDBerrors import *
|
||||
from aniDBAbstracter import Anime, Episode
|
||||
|
||||
version = 100
|
||||
|
||||
class Connection(threading.Thread):
|
||||
def __init__(self, clientname='adba', server='api.anidb.info', port=9000, myport=9876, user=None, password=None, session=None, log=False, logPrivate=False, keepAlive=False):
|
||||
super(Connection, self).__init__()
|
||||
# setting the log function
|
||||
self.logPrivate = logPrivate
|
||||
if type(log) in (FunctionType, MethodType):# if we get a function or a method use that.
|
||||
self.log = log
|
||||
self.logPrivate = True # true means sensitive data will not be NOT be logged ... yeah i know oO
|
||||
elif log:# if it something else (like True) use the own print_log
|
||||
self.log = self.print_log
|
||||
else:# dont log at all
|
||||
self.log = self.print_log_dummy
|
||||
|
||||
|
||||
self.link = AniDBLink(server, port, myport, self.log, logPrivate=self.logPrivate)
|
||||
self.link.session = session
|
||||
|
||||
self.clientname = clientname
|
||||
self.clientver = version
|
||||
|
||||
# from original lib
|
||||
self.mode = 1 #mode: 0=queue,1=unlock,2=callback
|
||||
|
||||
# to lock other threads out
|
||||
self.lock = threading.RLock()
|
||||
|
||||
# thread keep alive stuff
|
||||
self.keepAlive = keepAlive
|
||||
self.setDaemon(True)
|
||||
self.lastKeepAliveCheck = 0
|
||||
self.lastAuth = 0
|
||||
self._username = password
|
||||
self._password = user
|
||||
|
||||
self._iamALIVE = False
|
||||
|
||||
self.counter = 0
|
||||
self.counterAge = 0
|
||||
|
||||
def print_log(self, data):
|
||||
print(strftime("%Y-%m-%d %H:%M:%S", localtime(time())) + ": " + str(data))
|
||||
|
||||
def print_log_dummy(self, data):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.logout(cutConnection=True)
|
||||
|
||||
|
||||
def cut(self):
|
||||
self.link.stop()
|
||||
|
||||
def handle_response(self, response):
|
||||
if response.rescode in ('501', '506') and response.req.command != 'AUTH':
|
||||
self.log("seams like the last command got a not authed error back tring to reconnect now")
|
||||
if self._reAuthenticate():
|
||||
response.req.resp = None
|
||||
response = self.handle(response.req, response.req.callback)
|
||||
|
||||
|
||||
def handle(self, command, callback):
|
||||
|
||||
self.lock.acquire()
|
||||
if self.counterAge < (time() - 120): # the last request was older then 2 min reset delay and counter
|
||||
self.counter = 0
|
||||
self.link.delay = 2
|
||||
else: # something happend in the last 120 seconds
|
||||
if self.counter < 5:
|
||||
self.link.delay = 2 # short term "A Client MUST NOT send more than 0.5 packets per second (that's one packet every two seconds, not two packets a second!)"
|
||||
elif self.counter >= 5:
|
||||
self.link.delay = 6 # long term "A Client MUST NOT send more than one packet every four seconds over an extended amount of time."
|
||||
|
||||
if command.command not in ('AUTH', 'PING', 'ENCRYPT'):
|
||||
self.counterAge = time()
|
||||
self.counter += 1
|
||||
if self.keepAlive:
|
||||
self.authed()
|
||||
|
||||
def callback_wrapper(resp):
|
||||
self.handle_response(resp)
|
||||
if callback:
|
||||
callback(resp)
|
||||
|
||||
self.log("handling(" + str(self.counter) + "-" + str(self.link.delay) + ") command " + str(command.command))
|
||||
|
||||
#make live request
|
||||
command.authorize(self.mode, self.link.new_tag(), self.link.session, callback_wrapper)
|
||||
self.link.request(command)
|
||||
|
||||
#handle mode 1 (wait for response)
|
||||
if self.mode == 1:
|
||||
command.wait_response()
|
||||
try:
|
||||
command.resp
|
||||
except:
|
||||
self.lock.release()
|
||||
if self.link.banned:
|
||||
raise AniDBBannedError("User is banned")
|
||||
else:
|
||||
raise AniDBCommandTimeoutError("Command has timed out")
|
||||
|
||||
self.handle_response(command.resp)
|
||||
self.lock.release()
|
||||
return command.resp
|
||||
else:
|
||||
self.lock.release()
|
||||
|
||||
def authed(self, reAuthenticate=False):
|
||||
self.lock.acquire()
|
||||
authed = (self.link.session != None)
|
||||
if not authed and (reAuthenticate or self.keepAlive):
|
||||
self._reAuthenticate()
|
||||
authed = (self.link.session != None)
|
||||
self.lock.release()
|
||||
return authed
|
||||
|
||||
def _reAuthenticate(self):
|
||||
if self._username and self._password:
|
||||
self.log("auto re authenticating !")
|
||||
resp = self.auth(self._username, self._password)
|
||||
if resp.rescode not in ('500'):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _keep_alive(self):
|
||||
self.lastKeepAliveCheck = time()
|
||||
self.log("auto check !")
|
||||
# check every 30 minutes if the session is still valid
|
||||
# if not reauthenticate
|
||||
if self.lastAuth and time() - self.lastAuth > 1800:
|
||||
self.log("auto uptime !")
|
||||
self.uptime() # this will update the self.link.session and will refresh the session if it is still alive
|
||||
|
||||
if self.authed(): # if we are authed we set the time
|
||||
self.lastAuth = time()
|
||||
else: # if we aren't authed and we have the user and pw then reauthenticate
|
||||
self._reAuthenticate()
|
||||
|
||||
# issue a ping every 20 minutes after the last package
|
||||
# this ensures the connection will be kept alive
|
||||
if self.link.lastpacket and time() - self.link.lastpacket > 1200:
|
||||
self.log("auto ping !")
|
||||
self.ping()
|
||||
|
||||
|
||||
def run(self):
|
||||
while self.keepAlive:
|
||||
self._keep_alive()
|
||||
sleep(120)
|
||||
|
||||
|
||||
def auth(self, username, password, nat=None, mtu=None, callback=None):
|
||||
"""
|
||||
Login to AniDB UDP API
|
||||
|
||||
parameters:
|
||||
username - your anidb username
|
||||
password - your anidb password
|
||||
nat - if this is 1, response will have "address" in attributes with your "ip:port" (default:0)
|
||||
mtu - maximum transmission unit (max packet size) (default: 1400)
|
||||
|
||||
"""
|
||||
self.log("ok1")
|
||||
if self.keepAlive:
|
||||
self.log("ok2")
|
||||
self._username = username
|
||||
self._password = password
|
||||
if self.is_alive() == False:
|
||||
self.log("You wanted to keep this thing alive!")
|
||||
if self._iamALIVE == False:
|
||||
self.log("Starting thread now...")
|
||||
self.start()
|
||||
self._iamALIVE = True
|
||||
else:
|
||||
self.log("not starting thread seams like it is already running. this must be a _reAuthenticate")
|
||||
|
||||
|
||||
self.lastAuth = time()
|
||||
return self.handle(AuthCommand(username, password, 3, self.clientname, self.clientver, nat, 1, 'utf8', mtu), callback)
|
||||
|
||||
def logout(self, cutConnection=False, callback=None):
|
||||
"""
|
||||
Log out from AniDB UDP API
|
||||
|
||||
"""
|
||||
result = self.handle(LogoutCommand(), callback)
|
||||
if(cutConnection):
|
||||
self.cut()
|
||||
return result
|
||||
|
||||
def push(self, notify, msg, buddy=None, callback=None):
|
||||
"""
|
||||
Subscribe/unsubscribe to/from notifications
|
||||
|
||||
parameters:
|
||||
notify - Notifications about files added?
|
||||
msg - Notifications about message added?
|
||||
buddy - Notifications about buddy events?
|
||||
|
||||
structure of parameters:
|
||||
notify msg [buddy]
|
||||
|
||||
"""
|
||||
return self.handle(PushCommand(notify, msg, buddy), callback)
|
||||
|
||||
def pushack(self, nid, callback=None):
|
||||
"""
|
||||
Acknowledge notification (do this when you get 271-274)
|
||||
|
||||
parameters:
|
||||
nid - Notification packet id
|
||||
|
||||
structure of parameters:
|
||||
nid
|
||||
|
||||
"""
|
||||
return self.handle(PushAckCommand(nid), callback)
|
||||
|
||||
def notifyadd(self, aid=None, gid=None, type=None, priority=None, callback=None):
|
||||
"""
|
||||
Add a notification
|
||||
|
||||
parameters:
|
||||
aid - Anime id
|
||||
gid - Group id
|
||||
type - Type of notification: type=> 0=all, 1=new, 2=group, 3=complete
|
||||
priority - low = 0, medium = 1, high = 2 (unconfirmed)
|
||||
|
||||
structure of parameters:
|
||||
[aid={int}|gid={int}]&type={int}&priority={int}
|
||||
|
||||
"""
|
||||
|
||||
return self.handle(NotifyAddCommand(aid, gid, type, priority), callback)
|
||||
|
||||
|
||||
def notify(self, buddy=None, callback=None):
|
||||
"""
|
||||
Get number of pending notifications and messages
|
||||
|
||||
parameters:
|
||||
buddy - Also display number of online buddies
|
||||
|
||||
structure of parameters:
|
||||
[buddy]
|
||||
|
||||
"""
|
||||
return self.handle(NotifyCommand(buddy), callback)
|
||||
|
||||
def notifylist(self, callback=None):
|
||||
"""
|
||||
List all pending notifications/messages
|
||||
|
||||
"""
|
||||
return self.handle(NotifyListCommand(), callback)
|
||||
|
||||
def notifyget(self, type, id, callback=None):
|
||||
"""
|
||||
Get notification/message
|
||||
|
||||
parameters:
|
||||
type - (M=message, N=notification)
|
||||
id - message/notification id
|
||||
|
||||
structure of parameters:
|
||||
type id
|
||||
|
||||
"""
|
||||
return self.handle(NotifyGetCommand(type, id), callback)
|
||||
|
||||
def notifyack(self, type, id, callback=None):
|
||||
"""
|
||||
Mark message read or clear a notification
|
||||
|
||||
parameters:
|
||||
type - (M=message, N=notification)
|
||||
id - message/notification id
|
||||
|
||||
structure of parameters:
|
||||
type id
|
||||
|
||||
"""
|
||||
return self.handle(NotifyAckCommand(type, id), callback)
|
||||
|
||||
def buddyadd(self, uid=None, uname=None, callback=None):
|
||||
"""
|
||||
Add a user to your buddy list
|
||||
|
||||
parameters:
|
||||
uid - user id
|
||||
uname - name of the user
|
||||
|
||||
structure of parameters:
|
||||
(uid|uname)
|
||||
|
||||
"""
|
||||
return self.handle(BuddyAddCommand(uid, uname), callback)
|
||||
|
||||
def buddydel(self, uid, callback=None):
|
||||
"""
|
||||
Remove a user from your buddy list
|
||||
|
||||
parameters:
|
||||
uid - user id
|
||||
|
||||
structure of parameters:
|
||||
uid
|
||||
|
||||
"""
|
||||
return self.handle(BuddyDelCommand(uid), callback)
|
||||
|
||||
def buddyaccept(self, uid, callback=None):
|
||||
"""
|
||||
Accept user as buddy
|
||||
|
||||
parameters:
|
||||
uid - user id
|
||||
|
||||
structure of parameters:
|
||||
uid
|
||||
|
||||
"""
|
||||
return self.handle(BuddyAcceptCommand(uid), callback)
|
||||
|
||||
def buddydeny(self, uid, callback=None):
|
||||
"""
|
||||
Deny user as buddy
|
||||
|
||||
parameters:
|
||||
uid - user id
|
||||
|
||||
structure of parameters:
|
||||
uid
|
||||
|
||||
"""
|
||||
return self.handle(BuddyDenyCommand(uid), callback)
|
||||
|
||||
def buddylist(self, startat, callback=None):
|
||||
"""
|
||||
Retrieve your buddy list
|
||||
|
||||
parameters:
|
||||
startat - number of buddy to start listing from
|
||||
|
||||
structure of parameters:
|
||||
startat
|
||||
|
||||
"""
|
||||
return self.handle(BuddyListCommand(startat), callback)
|
||||
|
||||
def buddystate(self, startat, callback=None):
|
||||
"""
|
||||
Retrieve buddy states
|
||||
|
||||
parameters:
|
||||
startat - number of buddy to start listing from
|
||||
|
||||
structure of parameters:
|
||||
startat
|
||||
|
||||
"""
|
||||
return self.handle(BuddyStateCommand(startat), callback)
|
||||
|
||||
def anime(self, aid=None, aname=None, amask= -1, callback=None):
|
||||
"""
|
||||
Get information about an anime
|
||||
|
||||
parameters:
|
||||
aid - anime id
|
||||
aname - name of the anime
|
||||
amask - a bitfield describing what information you want about the anime
|
||||
|
||||
structure of parameters:
|
||||
(aid|aname) [amask]
|
||||
|
||||
structure of amask:
|
||||
|
||||
"""
|
||||
return self.handle(AnimeCommand(aid, aname, amask), callback)
|
||||
|
||||
def episode(self, eid=None, aid=None, aname=None, epno=None, callback=None):
|
||||
"""
|
||||
Get information about an episode
|
||||
|
||||
parameters:
|
||||
eid - episode id
|
||||
aid - anime id
|
||||
aname - name of the anime
|
||||
epno - number of the episode
|
||||
|
||||
structure of parameters:
|
||||
eid
|
||||
(aid|aname) epno
|
||||
|
||||
"""
|
||||
return self.handle(EpisodeCommand(eid, aid, aname, epno), callback)
|
||||
|
||||
def file(self, fid=None, size=None, ed2k=None, aid=None, aname=None, gid=None, gname=None, epno=None, fmask= -1, amask=0, callback=None):
|
||||
"""
|
||||
Get information about a file
|
||||
|
||||
parameters:
|
||||
fid - file id
|
||||
size - size of the file
|
||||
ed2k - ed2k-hash of the file
|
||||
aid - anime id
|
||||
aname - name of the anime
|
||||
gid - group id
|
||||
gname - name of the group
|
||||
epno - number of the episode
|
||||
fmask - a bitfield describing what information you want about the file
|
||||
amask - a bitfield describing what information you want about the anime
|
||||
|
||||
structure of parameters:
|
||||
fid [fmask] [amask]
|
||||
size ed2k [fmask] [amask]
|
||||
(aid|aname) (gid|gname) epno [fmask] [amask]
|
||||
|
||||
structure of fmask:
|
||||
bit key description
|
||||
0 - -
|
||||
1 aid aid
|
||||
2 eid eid
|
||||
3 gid gid
|
||||
4 lid lid
|
||||
5 - -
|
||||
6 - -
|
||||
7 - -
|
||||
8 state state
|
||||
9 size size
|
||||
10 ed2k ed2k
|
||||
11 md5 md5
|
||||
12 sha1 sha1
|
||||
13 crc32 crc32
|
||||
14 - -
|
||||
15 - -
|
||||
16 dublang dub language
|
||||
17 sublang sub language
|
||||
18 quality quality
|
||||
19 source source
|
||||
20 audiocodec audio codec
|
||||
21 audiobitrate audio bitrate
|
||||
22 videocodec video codec
|
||||
23 videobitrate video bitrate
|
||||
24 resolution video resolution
|
||||
25 filetype file type (extension)
|
||||
26 length length in seconds
|
||||
27 description description
|
||||
28 - -
|
||||
29 - -
|
||||
30 filename anidb file name
|
||||
31 - -
|
||||
|
||||
structure of amask:
|
||||
bit key description
|
||||
0 gname group name
|
||||
1 gshortname group short name
|
||||
2 - -
|
||||
3 - -
|
||||
4 - -
|
||||
5 - -
|
||||
6 - -
|
||||
7 - -
|
||||
8 epno epno
|
||||
9 epname ep english name
|
||||
10 epromaji ep romaji name
|
||||
11 epkanji ep kanji name
|
||||
12 - -
|
||||
13 - -
|
||||
14 - -
|
||||
15 - -
|
||||
16 totaleps anime total episodes
|
||||
17 lastep last episode nr (highest, not special)
|
||||
18 year year
|
||||
19 type type
|
||||
20 romaji romaji name
|
||||
21 kanji kanji name
|
||||
22 name english name
|
||||
23 othername other name
|
||||
24 shortnames short name list
|
||||
25 synonyms synonym list
|
||||
26 categories category list
|
||||
27 relatedaids related aid list
|
||||
28 producernames producer name list
|
||||
29 producerids producer id list
|
||||
30 - -
|
||||
31 - -
|
||||
|
||||
"""
|
||||
return self.handle(FileCommand(fid, size, ed2k, aid, aname, gid, gname, epno, fmask, amask), callback)
|
||||
|
||||
def group(self, gid=None, gname=None, callback=None):
|
||||
"""
|
||||
Get information about a group
|
||||
|
||||
parameters:
|
||||
gid - group id
|
||||
gname - name of the group
|
||||
|
||||
structure of parameters:
|
||||
(gid|gname)
|
||||
|
||||
"""
|
||||
return self.handle(GroupCommand(gid, gname), callback)
|
||||
|
||||
def groupstatus(self, aid=None, state=None, callback=None):
|
||||
"""
|
||||
Returns a list of group names and ranges of episodes released by the group for a given anime.
|
||||
parameters:
|
||||
aid - anime id
|
||||
state - If state is not supplied, groups with a completion state of 'ongoing', 'finished', or 'complete' are returned
|
||||
state values:
|
||||
1 -> ongoing
|
||||
2 -> stalled
|
||||
3 -> complete
|
||||
4 -> dropped
|
||||
5 -> finished
|
||||
6 -> specials only
|
||||
"""
|
||||
return self.handle(GroupstatusCommand(aid, state), callback)
|
||||
|
||||
def producer(self, pid=None, pname=None, callback=None):
|
||||
"""
|
||||
Get information about a producer
|
||||
|
||||
parameters:
|
||||
pid - producer id
|
||||
pname - name of the producer
|
||||
|
||||
structure of parameters:
|
||||
(pid|pname)
|
||||
|
||||
"""
|
||||
|
||||
return self.handle(ProducerCommand(pid, pname), callback)
|
||||
|
||||
def mylist(self, lid=None, fid=None, size=None, ed2k=None, aid=None, aname=None, gid=None, gname=None, epno=None, callback=None):
|
||||
"""
|
||||
Get information about your mylist
|
||||
|
||||
parameters:
|
||||
lid - mylist id
|
||||
fid - file id
|
||||
size - size of the file
|
||||
ed2k - ed2k-hash of the file
|
||||
aid - anime id
|
||||
aname - name of the anime
|
||||
gid - group id
|
||||
gname - name of the group
|
||||
epno - number of the episode
|
||||
|
||||
structure of parameters:
|
||||
lid
|
||||
fid
|
||||
size ed2k
|
||||
(aid|aname) (gid|gname) epno
|
||||
|
||||
"""
|
||||
return self.handle(MyListCommand(lid, fid, size, ed2k, aid, aname, gid, gname, epno), callback)
|
||||
|
||||
def mylistadd(self, lid=None, fid=None, size=None, ed2k=None, aid=None, aname=None, gid=None, gname=None, epno=None, edit=None, state=None, viewed=None, source=None, storage=None, other=None, callback=None):
|
||||
"""
|
||||
Add/Edit information to/in your mylist
|
||||
|
||||
parameters:
|
||||
lid - mylist id
|
||||
fid - file id
|
||||
size - size of the file
|
||||
ed2k - ed2k-hash of the file
|
||||
aid - anime id
|
||||
aname - name of the anime
|
||||
gid - group id
|
||||
gname - name of the group
|
||||
epno - number of the episode
|
||||
edit - whether to add to mylist or edit an existing entry (0=add,1=edit)
|
||||
state - the location of the file
|
||||
viewed - whether you have watched the file (0=unwatched,1=watched)
|
||||
source - where you got the file (bittorrent,dc++,ed2k,...)
|
||||
storage - for example the title of the cd you have this on
|
||||
other - other data regarding this file
|
||||
|
||||
structure of parameters:
|
||||
lid edit=1 [state viewed source storage other]
|
||||
fid [state viewed source storage other] [edit]
|
||||
size ed2k [state viewed source storage other] [edit]
|
||||
(aid|aname) (gid|gname) epno [state viewed source storage other]
|
||||
(aid|aname) edit=1 [(gid|gname) epno] [state viewed source storage other]
|
||||
|
||||
structure of state:
|
||||
value meaning
|
||||
0 unknown - state is unknown or the user doesn't want to provide this information
|
||||
1 on hdd - the file is stored on hdd
|
||||
2 on cd - the file is stored on cd
|
||||
3 deleted - the file has been deleted or is not available for other reasons (i.e. reencoded)
|
||||
|
||||
structure of epno:
|
||||
value meaning
|
||||
x target episode x
|
||||
0 target all episodes
|
||||
-x target all episodes upto x
|
||||
|
||||
"""
|
||||
return self.handle(MyListAddCommand(lid, fid, size, ed2k, aid, aname, gid, gname, epno, edit, state, viewed, source, storage, other), callback)
|
||||
|
||||
def mylistdel(self, lid=None, fid=None, aid=None, aname=None, gid=None, gname=None, epno=None, callback=None):
|
||||
"""
|
||||
Delete information from your mylist
|
||||
|
||||
parameters:
|
||||
lid - mylist id
|
||||
fid - file id
|
||||
size - size of the file
|
||||
ed2k - ed2k-hash of the file
|
||||
aid - anime id
|
||||
aname - name of the anime
|
||||
gid - group id
|
||||
gname - name of the group
|
||||
epno - number of the episode
|
||||
|
||||
structure of parameters:
|
||||
lid
|
||||
fid
|
||||
(aid|aname) (gid|gname) epno
|
||||
|
||||
"""
|
||||
return self.handle(MyListCommand(lid, fid, aid, aname, gid, gname, epno), callback)
|
||||
|
||||
def myliststats(self, callback=None):
|
||||
"""
|
||||
Get summary information of your mylist
|
||||
|
||||
"""
|
||||
return self.handle(MyListStatsCommand(), callback)
|
||||
|
||||
def vote(self, type, id=None, name=None, value=None, epno=None, callback=None):
|
||||
"""
|
||||
Rate an anime/episode/group
|
||||
|
||||
parameters:
|
||||
type - type of the vote
|
||||
id - anime/group id
|
||||
name - name of the anime/group
|
||||
value - the vote
|
||||
epno - number of the episode
|
||||
|
||||
structure of parameters:
|
||||
type (id|name) [value] [epno]
|
||||
|
||||
structure of type:
|
||||
value meaning
|
||||
1 rate an anime (episode if you also specify epno)
|
||||
2 rate an anime temporarily (you haven't watched it all)
|
||||
3 rate a group
|
||||
|
||||
structure of value:
|
||||
value meaning
|
||||
-x revoke vote
|
||||
0 get old vote
|
||||
100-1000 give vote
|
||||
|
||||
"""
|
||||
return self.handle(VoteCommand(type, id, name, value, epno), callback)
|
||||
|
||||
def randomanime(self, type, callback=None):
|
||||
"""
|
||||
Get information of random anime
|
||||
|
||||
parameters:
|
||||
type - where to take the random anime
|
||||
|
||||
structure of parameters:
|
||||
type
|
||||
|
||||
structure of type:
|
||||
value meaning
|
||||
0 db
|
||||
1 watched
|
||||
2 unwatched
|
||||
3 mylist
|
||||
|
||||
"""
|
||||
return self.handle(RandomAnimeCommand(type), callback)
|
||||
|
||||
def ping(self, callback=None):
|
||||
"""
|
||||
Test connectivity to AniDB UDP API
|
||||
|
||||
"""
|
||||
return self.handle(PingCommand(), callback)
|
||||
|
||||
def encrypt(self, user, apipassword, type=None, callback=None):
|
||||
"""
|
||||
Encrypt all future traffic
|
||||
|
||||
parameters:
|
||||
user - your username
|
||||
apipassword - your api password
|
||||
type - type of encoding (1=128bit AES)
|
||||
|
||||
structure of parameters:
|
||||
user [type]
|
||||
|
||||
"""
|
||||
return self.handle(EncryptCommand(user, apipassword, type), callback)
|
||||
|
||||
def encoding(self, name, callback=None):
|
||||
"""
|
||||
Change encoding used in messages
|
||||
|
||||
parameters:
|
||||
name - name of the encoding
|
||||
|
||||
structure of parameters:
|
||||
name
|
||||
|
||||
comments:
|
||||
DO NOT USE THIS!
|
||||
utf8 is the only encoding which will support all the text in anidb responses
|
||||
the responses have japanese, russian, french and probably other alphabets as well
|
||||
even if you can't display utf-8 locally, don't change the server-client -connections encoding
|
||||
rather, make python convert the encoding when you DISPLAY the text
|
||||
it's better that way, let it go as utf8 to databases etc. because then you've the real data stored
|
||||
|
||||
"""
|
||||
raise AniDBStupidUserError, "pylibanidb sets the encoding to utf8 as default and it's stupid to use any other encoding. you WILL lose some data if you use other encodings, and now you've been warned. you will need to modify the code yourself if you want to do something as stupid as changing the encoding"
|
||||
return self.handle(EncodingCommand(name), callback)
|
||||
|
||||
def sendmsg(self, to, title, body, callback=None):
|
||||
"""
|
||||
Send message
|
||||
|
||||
parameters:
|
||||
to - name of the user you want as the recipient
|
||||
title - title of the message
|
||||
body - the message
|
||||
|
||||
structure of parameters:
|
||||
to title body
|
||||
|
||||
"""
|
||||
return self.handle(SendMsgCommand(to, title, body), callback)
|
||||
|
||||
def user(self, user, callback=None):
|
||||
"""
|
||||
Retrieve user id
|
||||
|
||||
parameters:
|
||||
user - username of the user
|
||||
|
||||
structure of parameters:
|
||||
user
|
||||
|
||||
"""
|
||||
return self.handle(UserCommand(user), callback)
|
||||
|
||||
def uptime(self, callback=None):
|
||||
"""
|
||||
Retrieve server uptime
|
||||
|
||||
"""
|
||||
return self.handle(UptimeCommand(), callback)
|
||||
|
||||
def version(self, callback=None):
|
||||
"""
|
||||
Retrieve server version
|
||||
|
||||
"""
|
||||
return self.handle(VersionCommand(), callback)
|
293
lib/adba/aniDBAbstracter.py
Normal file
293
lib/adba/aniDBAbstracter.py
Normal file
@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from time import time, sleep
|
||||
import aniDBfileInfo as fileInfo
|
||||
import xml.etree.cElementTree as etree
|
||||
import os, re, string
|
||||
from aniDBmaper import AniDBMaper
|
||||
from aniDBtvDBmaper import TvDBMap
|
||||
from aniDBerrors import *
|
||||
|
||||
|
||||
|
||||
class aniDBabstractObject(object):
|
||||
|
||||
def __init__(self, aniDB, load=False):
|
||||
self.laoded = False
|
||||
self.set_connection(aniDB)
|
||||
if load:
|
||||
self.load_data()
|
||||
|
||||
def set_connection(self, aniDB):
|
||||
self.aniDB = aniDB
|
||||
if self.aniDB:
|
||||
self.log = self.aniDB.log
|
||||
else:
|
||||
self.log = self._fake_log()
|
||||
|
||||
def _fake_log(self, x=None):
|
||||
pass
|
||||
|
||||
def _fill(self, dataline):
|
||||
for key in dataline:
|
||||
try:
|
||||
tmpList = dataline[key].split("'")
|
||||
if len(tmpList) > 1:
|
||||
newList = []
|
||||
for i in tmpList:
|
||||
try:
|
||||
newList.append(int(i))
|
||||
except:
|
||||
newList.append(unicode(i, "utf-8"))
|
||||
self.__dict__[key] = newList
|
||||
continue
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
self.__dict__[key] = int(dataline[key])
|
||||
except:
|
||||
self.__dict__[key] = unicode(dataline[key], "utf-8")
|
||||
key = property(lambda x: dataline[key])
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return object.__getattribute__(self, name)
|
||||
except:
|
||||
return None
|
||||
|
||||
def _build_names(self):
|
||||
names = []
|
||||
names = self._easy_extend(names, self.english_name)
|
||||
names = self._easy_extend(names, self.short_name_list)
|
||||
names = self._easy_extend(names, self.synonym_list)
|
||||
names = self._easy_extend(names, self.other_name)
|
||||
|
||||
self.allNames = names
|
||||
|
||||
def _easy_extend(self, initialList, item):
|
||||
if item:
|
||||
if isinstance(item, list):
|
||||
initialList.extend(item)
|
||||
elif isinstance(item, basestring):
|
||||
initialList.append(item)
|
||||
|
||||
return initialList
|
||||
|
||||
|
||||
def load_data(self):
|
||||
return False
|
||||
|
||||
def add_notification(self):
|
||||
"""
|
||||
type - Type of notification: type=> 0=all, 1=new, 2=group, 3=complete
|
||||
priority - low = 0, medium = 1, high = 2 (unconfirmed)
|
||||
|
||||
"""
|
||||
if(self.aid):
|
||||
self.aniDB.notifyadd(aid=self.aid, type=1, priority=1)
|
||||
|
||||
|
||||
class Anime(aniDBabstractObject):
|
||||
def __init__(self, aniDB, name=None, aid=None, tvdbid=None, tvrageid=None, paramsA=None, autoCorrectName=False, load=False):
|
||||
|
||||
self.maper = AniDBMaper()
|
||||
self.tvDBMap = TvDBMap()
|
||||
self.allAnimeXML = None
|
||||
|
||||
self.name = name
|
||||
self.aid = aid
|
||||
self.tvdb_id = tvdbid
|
||||
|
||||
if self.tvdb_id and not self.aid:
|
||||
self.aid = self.tvDBMap.get_anidb_for_tvdb(self.tvdb_id)
|
||||
|
||||
if not (self.name or self.aid):
|
||||
raise AniDBIncorrectParameterError("No aid or name available")
|
||||
|
||||
if not self.aid:
|
||||
self.aid = self._get_aid_from_xml(self.name)
|
||||
if not self.name or autoCorrectName:
|
||||
self.name = self._get_name_from_xml(self.aid)
|
||||
|
||||
if not (self.name or self.aid):
|
||||
raise ValueError
|
||||
|
||||
if not self.tvdb_id:
|
||||
self.tvdb_id = self.tvDBMap.get_tvdb_for_anidb(self.aid)
|
||||
|
||||
if not paramsA:
|
||||
self.bitCode = "b2f0e0fc000000"
|
||||
self.params = self.maper.getAnimeCodesA(self.bitCode)
|
||||
else:
|
||||
self.paramsA = paramsA
|
||||
self.bitCode = self.maper.getAnimeBitsA(self.paramsA)
|
||||
|
||||
super(Anime, self).__init__(aniDB, load)
|
||||
|
||||
def load_data(self):
|
||||
"""load the data from anidb"""
|
||||
|
||||
if not (self.name or self.aid):
|
||||
raise ValueError
|
||||
|
||||
self.rawData = self.aniDB.anime(aid=self.aid, aname=self.name, amask=self.bitCode)
|
||||
if self.rawData.datalines:
|
||||
self._fill(self.rawData.datalines[0])
|
||||
self._builPreSequal()
|
||||
self.laoded = True
|
||||
|
||||
def get_groups(self):
|
||||
if not self.aid:
|
||||
return []
|
||||
self.rawData = self.aniDB.groupstatus(aid=self.aid)
|
||||
self.release_groups = []
|
||||
for line in self.rawData.datalines:
|
||||
self.release_groups.append({"name":unicode(line["name"], "utf-8"),
|
||||
"rating":line["rating"],
|
||||
"range":line["episode_range"]
|
||||
})
|
||||
return self.release_groups
|
||||
|
||||
#TODO: refactor and use the new functions in anidbFileinfo
|
||||
def _get_aid_from_xml(self, name):
|
||||
if not self.allAnimeXML:
|
||||
self.allAnimeXML = self._read_animetitels_xml()
|
||||
|
||||
regex = re.compile('( \(\d{4}\))|[%s]' % re.escape(string.punctuation)) # remove any punctuation and e.g. ' (2011)'
|
||||
#regex = re.compile('[%s]' % re.escape(string.punctuation)) # remove any punctuation and e.g. ' (2011)'
|
||||
name = regex.sub('', name.lower())
|
||||
lastAid = 0
|
||||
for element in self.allAnimeXML.getiterator():
|
||||
if element.get("aid", False):
|
||||
lastAid = int(element.get("aid"))
|
||||
if element.text:
|
||||
testname = regex.sub('', element.text.lower())
|
||||
|
||||
if testname == name:
|
||||
return lastAid
|
||||
return 0
|
||||
|
||||
#TODO: refactor and use the new functions in anidbFileinfo
|
||||
def _get_name_from_xml(self, aid, onlyMain=True):
|
||||
if not self.allAnimeXML:
|
||||
self.allAnimeXML = self._read_animetitels_xml()
|
||||
|
||||
for anime in self.allAnimeXML.findall("anime"):
|
||||
if int(anime.get("aid", False)) == aid:
|
||||
for title in anime.getiterator():
|
||||
currentLang = title.get("{http://www.w3.org/XML/1998/namespace}lang", False)
|
||||
currentType = title.get("type", False)
|
||||
if (currentLang == "en" and not onlyMain) or currentType == "main":
|
||||
return title.text
|
||||
return ""
|
||||
|
||||
|
||||
def _read_animetitels_xml(self, path=None):
|
||||
if not path:
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "animetitles.xml")
|
||||
|
||||
f = open(path, "r")
|
||||
allAnimeXML = etree.ElementTree(file=f)
|
||||
return allAnimeXML
|
||||
|
||||
def _builPreSequal(self):
|
||||
if self.related_aid_list and self.related_aid_type:
|
||||
try:
|
||||
for i in range(len(self.related_aid_list)):
|
||||
if self.related_aid_type[i] == 2:
|
||||
self.__dict__["prequal"] = self.related_aid_list[i]
|
||||
elif self.related_aid_type[i] == 1:
|
||||
self.__dict__["sequal"] = self.related_aid_list[i]
|
||||
except:
|
||||
if self.related_aid_type == 2:
|
||||
self.__dict__["prequal"] = self.related_aid_list
|
||||
elif self.str_related_aid_type == 1:
|
||||
self.__dict__["sequal"] = self.related_aid_list
|
||||
|
||||
|
||||
|
||||
class Episode(aniDBabstractObject):
|
||||
|
||||
def __init__(self, aniDB, number=None, epid=None, filePath=None, fid=None, epno=None, paramsA=None, paramsF=None, load=False, calculate=False):
|
||||
if not aniDB and not number and not epid and not file and not fid:
|
||||
return None
|
||||
|
||||
self.maper = AniDBMaper()
|
||||
self.epid = epid
|
||||
self.filePath = filePath
|
||||
self.fid = fid
|
||||
self.epno = epno
|
||||
if calculate:
|
||||
(self.ed2k, self.size) = self._calculate_file_stuff(self.filePath)
|
||||
|
||||
|
||||
if not paramsA:
|
||||
self.bitCodeA = "C000F0C0"
|
||||
self.paramsA = self.maper.getFileCodesA(self.bitCodeA)
|
||||
else:
|
||||
self.paramsA = paramsA
|
||||
self.bitCodeA = self.maper.getFileBitsA(self.paramsA)
|
||||
|
||||
if not paramsF:
|
||||
self.bitCodeF = "7FF8FEF8"
|
||||
self.paramsF = self.maper.getFileCodesF(self.bitCodeF)
|
||||
else:
|
||||
self.paramsF = paramsF
|
||||
self.bitCodeF = self.maper.getFileBitsF(self.paramsF)
|
||||
|
||||
super(Episode, self).__init__(aniDB, load)
|
||||
|
||||
def load_data(self):
|
||||
"""load the data from anidb"""
|
||||
if self.filePath and not (self.ed2k or self.size):
|
||||
(self.ed2k, self.size) = self._calculate_file_stuff(self.filePath)
|
||||
|
||||
self.rawData = self.aniDB.file(fid=self.fid, size=self.size, ed2k=self.ed2k, aid=self.aid, aname=None, gid=None, gname=None, epno=self.epno, fmask=self.bitCodeF, amask=self.bitCodeA)
|
||||
self._fill(self.rawData.datalines[0])
|
||||
self._build_names()
|
||||
self.laoded = True
|
||||
|
||||
def add_to_mylist(self, status=None):
|
||||
"""
|
||||
status:
|
||||
0 unknown - state is unknown or the user doesn't want to provide this information (default)
|
||||
1 on hdd - the file is stored on hdd
|
||||
2 on cd - the file is stored on cd
|
||||
3 deleted - the file has been deleted or is not available for other reasons (i.e. reencoded)
|
||||
|
||||
"""
|
||||
if self.filePath and not (self.ed2k or self.size):
|
||||
(self.ed2k, self.size) = self._calculate_file_stuff(self.filePath)
|
||||
|
||||
try:
|
||||
self.aniDB.mylistadd(size=self.size, ed2k=self.ed2k, state=status)
|
||||
except Exception, e :
|
||||
self.log(u"exception msg: " + str(e))
|
||||
else:
|
||||
# TODO: add the name or something
|
||||
self.log(u"Added the episode to anidb")
|
||||
|
||||
|
||||
def _calculate_file_stuff(self, filePath):
|
||||
if not filePath:
|
||||
return (None, None)
|
||||
self.log("Calculating the ed2k. Please wait...")
|
||||
ed2k = fileInfo.get_file_hash(filePath)
|
||||
size = fileInfo.get_file_size(filePath)
|
||||
return (ed2k, size)
|
||||
|
388
lib/adba/aniDBcommands.py
Normal file
388
lib/adba/aniDBcommands.py
Normal file
@ -0,0 +1,388 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from threading import Lock
|
||||
from aniDBresponses import *
|
||||
from aniDBerrors import *
|
||||
|
||||
class Command:
|
||||
queue={None:None}
|
||||
def __init__(self,command,**parameters):
|
||||
self.command=command
|
||||
self.parameters=parameters
|
||||
self.raw=self.flatten(command,parameters)
|
||||
|
||||
self.mode=None
|
||||
self.callback=None
|
||||
self.waiter=Lock()
|
||||
self.waiter.acquire()
|
||||
|
||||
def __repr__(self):
|
||||
return "Command(%s,%s) %s\n%s\n"%(repr(self.tag),repr(self.command),repr(self.parameters),self.raw_data())
|
||||
|
||||
def authorize(self,mode,tag,session,callback):
|
||||
self.mode=mode
|
||||
self.callback=callback
|
||||
self.tag=tag
|
||||
self.session=session
|
||||
|
||||
self.parameters['tag']=tag
|
||||
self.parameters['s']=session
|
||||
|
||||
def handle(self,resp):
|
||||
self.resp=resp
|
||||
if self.mode==1:
|
||||
self.waiter.release()
|
||||
elif self.mode==2:
|
||||
self.callback(resp)
|
||||
|
||||
def wait_response(self):
|
||||
self.waiter.acquire()
|
||||
|
||||
def flatten(self,command,parameters):
|
||||
tmp=[]
|
||||
for key,value in parameters.iteritems():
|
||||
if value==None:
|
||||
continue
|
||||
tmp.append("%s=%s"%(self.escape(key),self.escape(value)))
|
||||
return ' '.join([command,'&'.join(tmp)])
|
||||
|
||||
def escape(self,data):
|
||||
return str(data).replace('&','&')
|
||||
|
||||
def raw_data(self):
|
||||
self.raw=self.flatten(self.command,self.parameters)
|
||||
return self.raw
|
||||
|
||||
def cached(self,interface,database):
|
||||
return None
|
||||
|
||||
def cache(self,interface,database):
|
||||
pass
|
||||
|
||||
#first run
|
||||
class AuthCommand(Command):
|
||||
def __init__(self,username,password,protover,client,clientver,nat=None,comp=None,enc=None,mtu=None):
|
||||
parameters={'user':username,'pass':password,'protover':protover,'client':client,'clientver':clientver,'nat':nat,'comp':comp,'enc':enc,'mtu':mtu}
|
||||
Command.__init__(self,'AUTH',**parameters)
|
||||
|
||||
class LogoutCommand(Command):
|
||||
def __init__(self):
|
||||
Command.__init__(self,'LOGOUT')
|
||||
|
||||
#third run (at the same time as second)
|
||||
class PushCommand(Command):
|
||||
def __init__(self,notify,msg,buddy=None):
|
||||
parameters={'notify':notify,'msg':msg,'buddy':buddy}
|
||||
Command.__init__(self,'PUSH',**parameters)
|
||||
|
||||
class PushAckCommand(Command):
|
||||
def __init__(self,nid):
|
||||
parameters={'nid':nid}
|
||||
Command.__init__(self,'PUSHACK',**parameters)
|
||||
|
||||
class NotifyAddCommand(Command):
|
||||
def __init__(self,aid=None,gid=None,type=None,priority=None):
|
||||
if not (aid or gid) or (aid and gid):
|
||||
raise AniDBIncorrectParameterError,"You must provide aid OR gid for NOTIFICATIONADD command"
|
||||
parameters={'aid':aid,"gid":gid,"type":type,"priority":priority}
|
||||
Command.__init__(self,'NOTIFICATIONADD',**parameters)
|
||||
|
||||
class NotifyCommand(Command):
|
||||
def __init__(self,buddy=None):
|
||||
parameters={'buddy':buddy}
|
||||
Command.__init__(self,'NOTIFY',**parameters)
|
||||
|
||||
class NotifyListCommand(Command):
|
||||
def __init__(self):
|
||||
Command.__init__(self,'NOTIFYLIST')
|
||||
|
||||
class NotifyGetCommand(Command):
|
||||
def __init__(self,type,id):
|
||||
parameters={'type':type,'id':id}
|
||||
Command.__init__(self,'NOTIFYGET',**parameters)
|
||||
|
||||
class NotifyAckCommand(Command):
|
||||
def __init__(self,type,id):
|
||||
parameters={'type':type,'id':id}
|
||||
Command.__init__(self,'NOTIFYACK',**parameters)
|
||||
|
||||
class BuddyAddCommand(Command):
|
||||
def __init__(self,uid=None,uname=None):
|
||||
if not (uid or uname) or (uid and uname):
|
||||
raise AniDBIncorrectParameterError,"You must provide <u(id|name)> for BUDDYADD command"
|
||||
parameters={'uid':uid,'uname':uname.lower()}
|
||||
Command.__init__(self,'BUDDYADD',**parameters)
|
||||
|
||||
class BuddyDelCommand(Command):
|
||||
def __init__(self,uid):
|
||||
parameters={'uid':uid}
|
||||
Command.__init__(self,'BUDDYDEL',**parameters)
|
||||
|
||||
class BuddyAcceptCommand(Command):
|
||||
def __init__(self,uid):
|
||||
parameters={'uid':uid}
|
||||
Command.__init__(self,'BUDDYACCEPT',**parameters)
|
||||
|
||||
class BuddyDenyCommand(Command):
|
||||
def __init__(self,uid):
|
||||
parameters={'uid':uid}
|
||||
Command.__init__(self,'BUDDYDENY',**parameters)
|
||||
|
||||
class BuddyListCommand(Command):
|
||||
def __init__(self,startat):
|
||||
parameters={'startat':startat}
|
||||
Command.__init__(self,'BUDDYLIST',**parameters)
|
||||
|
||||
class BuddyStateCommand(Command):
|
||||
def __init__(self,startat):
|
||||
parameters={'startat':startat}
|
||||
Command.__init__(self,'BUDDYSTATE',**parameters)
|
||||
|
||||
#first run
|
||||
class AnimeCommand(Command):
|
||||
def __init__(self,aid=None,aname=None,amask=None):
|
||||
if not (aid or aname):
|
||||
raise AniDBIncorrectParameterError,"You must provide <a(id|name)> for ANIME command"
|
||||
parameters={'aid':aid,'aname':aname,'amask':amask}
|
||||
Command.__init__(self,'ANIME',**parameters)
|
||||
|
||||
class EpisodeCommand(Command):
|
||||
def __init__(self,eid=None,aid=None,aname=None,epno=None):
|
||||
if not (eid or ((aname or aid) and epno)) or (aname and aid) or (eid and (aname or aid or epno)):
|
||||
raise AniDBIncorrectParameterError,"You must provide <eid XOR a(id|name)+epno> for EPISODE command"
|
||||
parameters={'eid':eid,'aid':aid,'aname':aname,'epno':epno}
|
||||
Command.__init__(self,'EPISODE',**parameters)
|
||||
|
||||
class FileCommand(Command):
|
||||
def __init__(self,fid=None,size=None,ed2k=None,aid=None,aname=None,gid=None,gname=None,epno=None,fmask=None,amask=None):
|
||||
if not (fid or (size and ed2k) or ((aid or aname) and (gid or gname) and epno)) or (fid and (size or ed2k or aid or aname or gid or gname or epno)) or ((size and ed2k) and (fid or aid or aname or gid or gname or epno)) or (((aid or aname) and (gid or gname) and epno) and (fid or size or ed2k)) or (aid and aname) or (gid and gname):
|
||||
raise AniDBIncorrectParameterError,"You must provide <fid XOR size+ed2k XOR a(id|name)+g(id|name)+epno> for FILE command"
|
||||
parameters={'fid':fid,'size':size,'ed2k':ed2k,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno,'fmask':fmask,'amask':amask}
|
||||
Command.__init__(self,'FILE',**parameters)
|
||||
|
||||
class GroupCommand(Command):
|
||||
def __init__(self,gid=None,gname=None):
|
||||
if not (gid or gname) or (gid and gname):
|
||||
raise AniDBIncorrectParameterError,"You must provide <g(id|name)> for GROUP command"
|
||||
parameters={'gid':gid,'gname':gname}
|
||||
Command.__init__(self,'GROUP',**parameters)
|
||||
|
||||
class GroupstatusCommand(Command):
|
||||
def __init__(self,aid=None,status=None):
|
||||
if not aid:
|
||||
raise AniDBIncorrectParameterError,"You must provide aid for GROUPSTATUS command"
|
||||
parameters={'aid':aid,'status':status}
|
||||
Command.__init__(self,'GROUPSTATUS',**parameters)
|
||||
|
||||
class ProducerCommand(Command):
|
||||
def __init__(self,pid=None,pname=None):
|
||||
if not (pid or pname) or (pid and pname):
|
||||
raise AniDBIncorrectParameterError,"You must provide <p(id|name)> for PRODUCER command"
|
||||
parameters={'pid':pid,'pname':pname}
|
||||
Command.__init__(self,'PRODUCER',**parameters)
|
||||
|
||||
def cached(self,intr,db):
|
||||
pid=self.parameters['pid']
|
||||
pname=self.parameters['pname']
|
||||
|
||||
codes=('pid', 'name', 'shortname', 'othername', 'type', 'pic', 'url')
|
||||
names=','.join([code for code in codes if code!=''])
|
||||
ruleholder=(pid and 'pid=%s' or '(name=%s OR shortname=%s OR othername=%s)')
|
||||
rulevalues=(pid and [pid] or [pname,pname,pname])
|
||||
|
||||
rows=db.select('ptb',names,ruleholder+" AND status&8",*rulevalues)
|
||||
|
||||
if len(rows)>1:
|
||||
raise AniDBInternalError,"It shouldn't be possible for database to return more than 1 line for PRODUCER cache"
|
||||
elif not len(rows):
|
||||
return None
|
||||
else:
|
||||
resp=ProducerResponse(self,None,'245','CACHED PRODUCER',[list(rows[0])])
|
||||
resp.parse()
|
||||
return resp
|
||||
|
||||
def cache(self,intr,db):
|
||||
if self.resp.rescode!='245' or self.cached(intr,db):
|
||||
return
|
||||
|
||||
codes=('pid', 'name', 'shortname', 'othername', 'type', 'pic', 'url')
|
||||
if len(db.select('ptb','pid','pid=%s',self.resp.datalines[0]['pid'])):
|
||||
sets='status=status|15,'+','.join([code+'=%s' for code in codes if code!=''])
|
||||
values=[self.resp.datalines[0][code] for code in codes if code!='']+[self.resp.datalines[0]['pid']]
|
||||
|
||||
db.update('ptb',sets,'pid=%s',*values)
|
||||
else:
|
||||
names='status,'+','.join([code for code in codes if code!=''])
|
||||
valueholders='0,'+','.join(['%s'for code in codes if code!=''])
|
||||
values=[self.resp.datalines[0][code] for code in codes if code!='']
|
||||
|
||||
db.insert('ptb',names,valueholders,*values)
|
||||
|
||||
class MyListCommand(Command):
|
||||
def __init__(self,lid=None,fid=None,size=None,ed2k=None,aid=None,aname=None,gid=None,gname=None,epno=None):
|
||||
if not (lid or fid or (size and ed2k) or (aid or aname)) or (lid and (fid or size or ed2k or aid or aname or gid or gname or epno)) or (fid and (lid or size or ed2k or aid or aname or gid or gname or epno)) or ((size and ed2k) and (lid or fid or aid or aname or gid or gname or epno)) or ((aid or aname) and (lid or fid or size or ed2k)) or (aid and aname) or (gid and gname):
|
||||
raise AniDBIncorrectParameterError,"You must provide <lid XOR fid XOR size+ed2k XOR a(id|name)+g(id|name)+epno> for MYLIST command"
|
||||
parameters={'lid':lid,'fid':fid,'size':size,'ed2k':ed2k,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno}
|
||||
Command.__init__(self,'MYLIST',**parameters)
|
||||
|
||||
def cached(self,intr,db):
|
||||
lid=self.parameters['lid']
|
||||
fid=self.parameters['fid']
|
||||
size=self.parameters['size']
|
||||
ed2k=self.parameters['ed2k']
|
||||
aid=self.parameters['aid']
|
||||
aname=self.parameters['aname']
|
||||
gid=self.parameters['gid']
|
||||
gname=self.parameters['gname']
|
||||
epno=self.parameters['epno']
|
||||
|
||||
names=','.join([code for code in MylistResponse(None,None,None,None,[]).codetail if code!=''])
|
||||
|
||||
if lid:
|
||||
ruleholder="lid=%s"
|
||||
rulevalues=[lid]
|
||||
elif fid or size or ed2k:
|
||||
resp=intr.file(fid=fid,size=size,ed2k=ed2k)
|
||||
if resp.rescode!='220':
|
||||
resp=NoSuchMylistResponse(self,None,'321','NO SUCH ENTRY (FILE NOT FOUND)',[])
|
||||
resp.parse()
|
||||
return resp
|
||||
fid=resp.datalines[0]['fid']
|
||||
|
||||
ruleholder="fid=%s"
|
||||
rulevalues=[fid]
|
||||
else:
|
||||
resp=intr.anime(aid=aid,aname=aname)
|
||||
if resp.rescode!='230':
|
||||
resp=NoSuchFileResponse(self,None,'321','NO SUCH ENTRY (ANIME NOT FOUND)',[])
|
||||
resp.parse()
|
||||
return resp
|
||||
aid=resp.datalines[0]['aid']
|
||||
|
||||
resp=intr.group(gid=gid,gname=gname)
|
||||
if resp.rescode!='250':
|
||||
resp=NoSuchFileResponse(self,None,'321','NO SUCH ENTRY (GROUP NOT FOUND)',[])
|
||||
resp.parse()
|
||||
return resp
|
||||
gid=resp.datalines[0]['gid']
|
||||
|
||||
resp=intr.episode(aid=aid,epno=epno)
|
||||
if resp.rescode!='240':
|
||||
resp=NoSuchFileResponse(self,None,'321','NO SUCH ENTRY (EPISODE NOT FOUND)',[])
|
||||
resp.parse()
|
||||
return resp
|
||||
eid=resp.datalines[0]['eid']
|
||||
|
||||
ruleholder="aid=%s AND eid=%s AND gid=%s"
|
||||
rulevalues=[aid,eid,gid]
|
||||
|
||||
rows=db.select('ltb',names,ruleholder+" AND status&8",*rulevalues)
|
||||
|
||||
if len(rows)>1:
|
||||
#resp=MultipleFilesFoundResponse(self,None,'322','CACHED MULTIPLE FILES FOUND',/*get fids from rows, not gonna do this as you haven't got a real cache out of these..*/)
|
||||
return None
|
||||
elif not len(rows):
|
||||
return None
|
||||
else:
|
||||
resp=MylistResponse(self,None,'221','CACHED MYLIST',[list(rows[0])])
|
||||
resp.parse()
|
||||
return resp
|
||||
|
||||
def cache(self,intr,db):
|
||||
if self.resp.rescode!='221' or self.cached(intr,db):
|
||||
return
|
||||
|
||||
codes=MylistResponse(None,None,None,None,[]).codetail
|
||||
if len(db.select('ltb','lid','lid=%s',self.resp.datalines[0]['lid'])):
|
||||
sets='status=status|15,'+','.join([code+'=%s' for code in codes if code!=''])
|
||||
values=[self.resp.datalines[0][code] for code in codes if code!='']+[self.resp.datalines[0]['lid']]
|
||||
|
||||
db.update('ltb',sets,'lid=%s',*values)
|
||||
else:
|
||||
names='status,'+','.join([code for code in codes if code!=''])
|
||||
valueholders='15,'+','.join(['%s' for code in codes if code!=''])
|
||||
values=[self.resp.datalines[0][code] for code in codes if code!='']
|
||||
|
||||
db.insert('ltb',names,valueholders,*values)
|
||||
|
||||
class MyListAddCommand(Command):
|
||||
def __init__(self,lid=None,fid=None,size=None,ed2k=None,aid=None,aname=None,gid=None,gname=None,epno=None,edit=None,state=None,viewed=None,source=None,storage=None,other=None):
|
||||
if not (lid or fid or (size and ed2k) or ((aid or aname) and (gid or gname))) or (lid and (fid or size or ed2k or aid or aname or gid or gname or epno)) or (fid and (lid or size or ed2k or aid or aname or gid or gname or epno)) or ((size and ed2k) and (lid or fid or aid or aname or gid or gname or epno)) or (((aid or aname) and (gid or gname)) and (lid or fid or size or ed2k)) or (aid and aname) or (gid and gname) or (lid and not edit):
|
||||
raise AniDBIncorrectParameterError,"You must provide <lid XOR fid XOR size+ed2k XOR a(id|name)+g(id|name)+epno> for MYLISTADD command"
|
||||
parameters={'lid':lid,'fid':fid,'size':size,'ed2k':ed2k,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno,'edit':edit,'state':state,'viewed':viewed,'source':source,'storage':storage,'other':other}
|
||||
Command.__init__(self,'MYLISTADD',**parameters)
|
||||
|
||||
class MyListDelCommand(Command):
|
||||
def __init__(self,lid=None,fid=None,aid=None,aname=None,gid=None,gname=None,epno=None):
|
||||
if not (lid or fid or ((aid or aname) and (gid or gname) and epno)) or (lid and (fid or aid or aname or gid or gname or epno)) or (fid and (lid or aid or aname or gid or gname or epno)) or (((aid or aname) and (gid or gname) and epno) and (lid or fid)) or (aid and aname) or (gid and gname):
|
||||
raise AniDBIncorrectParameterError,"You must provide <lid+edit=1 XOR fid XOR a(id|name)+g(id|name)+epno> for MYLISTDEL command"
|
||||
parameters={'lid':lid,'fid':fid,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno}
|
||||
Command.__init__(self,'MYLISTDEL',**parameters)
|
||||
|
||||
class MyListStatsCommand(Command):
|
||||
def __init__(self):
|
||||
Command.__init__(self,'MYLISTSTATS')
|
||||
|
||||
class VoteCommand(Command):
|
||||
def __init__(self,type,id=None,name=None,value=None,epno=None):
|
||||
if not (id or name) or (id and name):
|
||||
raise AniDBIncorrectParameterError,"You must provide <(id|name)> for VOTE command"
|
||||
parameters={'type':type,'id':id,'name':name,'value':value,'epno':epno}
|
||||
Command.__init__(self,'VOTE',**parameters)
|
||||
|
||||
class RandomAnimeCommand(Command):
|
||||
def __init__(self,type):
|
||||
parameters={'type':type}
|
||||
Command.__init__(self,'RANDOMANIME',**parameters)
|
||||
|
||||
class PingCommand(Command):
|
||||
def __init__(self):
|
||||
Command.__init__(self,'PING')
|
||||
|
||||
#second run
|
||||
class EncryptCommand(Command):
|
||||
def __init__(self,user,apipassword,type):
|
||||
self.apipassword=apipassword
|
||||
parameters={'user':user.lower(),'type':type}
|
||||
Command.__init__(self,'ENCRYPT',**parameters)
|
||||
|
||||
class EncodingCommand(Command):
|
||||
def __init__(self,name):
|
||||
parameters={'name':type}
|
||||
Command.__init__(self,'ENCODING',**parameters)
|
||||
|
||||
class SendMsgCommand(Command):
|
||||
def __init__(self,to,title,body):
|
||||
if len(title)>50 or len(body)>900:
|
||||
raise AniDBIncorrectParameterError,"Title must not be longer than 50 chars and body must not be longer than 900 chars for SENDMSG command"
|
||||
parameters={'to':to.lower(),'title':title,'body':body}
|
||||
Command.__init__(self,'SENDMSG',**parameters)
|
||||
|
||||
class UserCommand(Command):
|
||||
def __init__(self,user):
|
||||
parameters={'user':user}
|
||||
Command.__init__(self,'USER',**parameters)
|
||||
|
||||
class UptimeCommand(Command):
|
||||
def __init__(self):
|
||||
Command.__init__(self,'UPTIME')
|
||||
|
||||
class VersionCommand(Command):
|
||||
def __init__(self):
|
||||
Command.__init__(self,'VERSION')
|
||||
|
37
lib/adba/aniDBerrors.py
Normal file
37
lib/adba/aniDBerrors.py
Normal file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class AniDBError(Exception):
|
||||
pass
|
||||
|
||||
class AniDBIncorrectParameterError(AniDBError):
|
||||
pass
|
||||
|
||||
class AniDBCommandTimeoutError(AniDBError):
|
||||
pass
|
||||
|
||||
class AniDBMustAuthError(AniDBError):
|
||||
pass
|
||||
|
||||
class AniDBPacketCorruptedError(AniDBError):
|
||||
pass
|
||||
|
||||
class AniDBBannedError(AniDBError):
|
||||
pass
|
||||
|
||||
class AniDBInternalError(AniDBError):
|
||||
pass
|
75
lib/adba/aniDBfileInfo.py
Normal file
75
lib/adba/aniDBfileInfo.py
Normal file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import with_statement
|
||||
import hashlib
|
||||
import os
|
||||
import xml.etree.cElementTree as etree
|
||||
|
||||
|
||||
# http://www.radicand.org/blog/orz/2010/2/21/edonkey2000-hash-in-python/
|
||||
def get_file_hash(filePath):
|
||||
""" Returns the ed2k hash of a given file."""
|
||||
if not filePath:
|
||||
return None
|
||||
md4 = hashlib.new('md4').copy
|
||||
|
||||
def gen(f):
|
||||
while True:
|
||||
x = f.read(9728000)
|
||||
if x: yield x
|
||||
else: return
|
||||
|
||||
def md4_hash(data):
|
||||
m = md4()
|
||||
m.update(data)
|
||||
return m
|
||||
|
||||
with open(filePath, 'rb') as f:
|
||||
a = gen(f)
|
||||
hashes = [md4_hash(data).digest() for data in a]
|
||||
if len(hashes) == 1:
|
||||
return hashes[0].encode("hex")
|
||||
else: return md4_hash(reduce(lambda a,d: a + d, hashes, "")).hexdigest()
|
||||
|
||||
|
||||
def get_file_size(path):
|
||||
size = os.path.getsize(path)
|
||||
return size
|
||||
|
||||
|
||||
|
||||
def read_anidb_xml(filePath):
|
||||
if not filePath:
|
||||
filePath = os.path.join(os.path.dirname(os.path.abspath( __file__ )), "animetitles.xml")
|
||||
return read_xml_into_etree(filePath)
|
||||
|
||||
|
||||
def read_tvdb_map_xml(filePath):
|
||||
if not filePath:
|
||||
filePath = os.path.join(os.path.dirname(os.path.abspath( __file__ )), "anime-list.xml")
|
||||
return read_xml_into_etree(filePath)
|
||||
|
||||
|
||||
def read_xml_into_etree(filePath):
|
||||
if not filePath:
|
||||
return None
|
||||
|
||||
f = open(filePath,"r")
|
||||
xmlASetree = etree.ElementTree(file = f)
|
||||
return xmlASetree
|
||||
|
218
lib/adba/aniDBlink.py
Normal file
218
lib/adba/aniDBlink.py
Normal file
@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import socket, sys, zlib
|
||||
from time import time, sleep
|
||||
import threading
|
||||
from aniDBresponses import ResponseResolver
|
||||
from aniDBerrors import *
|
||||
|
||||
|
||||
class AniDBLink(threading.Thread):
|
||||
def __init__(self, server, port, myport, logFunction, delay=2, timeout=20, logPrivate=False):
|
||||
super(AniDBLink, self).__init__()
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.target = (server, port)
|
||||
self.timeout = timeout
|
||||
|
||||
self.myport = 0
|
||||
self.bound = self.connectSocket(myport, self.timeout)
|
||||
|
||||
self.cmd_queue = {None:None}
|
||||
self.resp_tagged_queue = {}
|
||||
self.resp_untagged_queue = []
|
||||
self.tags = []
|
||||
self.lastpacket = time()
|
||||
self.delay = delay
|
||||
self.session = None
|
||||
self.banned = False
|
||||
self.crypt = None
|
||||
|
||||
self.log = logFunction
|
||||
self.logPrivate = logPrivate
|
||||
|
||||
self._stop = threading.Event()
|
||||
self._quiting = False
|
||||
self.setDaemon(True)
|
||||
self.start()
|
||||
|
||||
def connectSocket(self, myport, timeout):
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self.sock.settimeout(timeout)
|
||||
portlist = [myport] + [7654]
|
||||
for port in portlist:
|
||||
try:
|
||||
self.sock.bind(('', port))
|
||||
except:
|
||||
continue
|
||||
else:
|
||||
self.myport = port
|
||||
return True
|
||||
else:
|
||||
return False;
|
||||
|
||||
def disconnectSocket(self):
|
||||
self.sock.close()
|
||||
|
||||
def stop (self):
|
||||
self.log("Releasing socket and stopping link thread")
|
||||
self._quiting = True
|
||||
self.disconnectSocket()
|
||||
self._stop.set()
|
||||
|
||||
def stopped (self):
|
||||
return self._stop.isSet()
|
||||
|
||||
def print_log(self, data):
|
||||
print data
|
||||
|
||||
def print_log_dummy(self, data):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
while not self._quiting:
|
||||
try:
|
||||
data = self.sock.recv(8192)
|
||||
except socket.timeout:
|
||||
self._handle_timeouts()
|
||||
|
||||
continue
|
||||
self.log("NetIO < %s" % repr(data))
|
||||
try:
|
||||
for i in range(2):
|
||||
try:
|
||||
tmp = data
|
||||
resp = None
|
||||
if tmp[:2] == '\x00\x00':
|
||||
tmp = zlib.decompressobj().decompress(tmp[2:])
|
||||
self.log("UnZip | %s" % repr(tmp))
|
||||
resp = ResponseResolver(tmp)
|
||||
except:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
self.crypt = None
|
||||
self.session = None
|
||||
else:
|
||||
break
|
||||
if not resp:
|
||||
raise AniDBPacketCorruptedError, "Either decrypting, decompressing or parsing the packet failed"
|
||||
cmd = self._cmd_dequeue(resp)
|
||||
resp = resp.resolve(cmd)
|
||||
resp.parse()
|
||||
if resp.rescode in ('200', '201'):
|
||||
self.session = resp.attrs['sesskey']
|
||||
if resp.rescode in ('209',):
|
||||
print "sorry encryption is not supported"
|
||||
raise
|
||||
#self.crypt=aes(md5(resp.req.apipassword+resp.attrs['salt']).digest())
|
||||
if resp.rescode in ('203', '403', '500', '501', '503', '506'):
|
||||
self.session = None
|
||||
self.crypt = None
|
||||
if resp.rescode in ('504', '555'):
|
||||
self.banned = True
|
||||
print "AniDB API informs that user or client is banned:", resp.resstr
|
||||
resp.handle()
|
||||
if not cmd or not cmd.mode:
|
||||
self._resp_queue(resp)
|
||||
else:
|
||||
self.tags.remove(resp.restag)
|
||||
except:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
print "Avoiding flood by paranoidly panicing: Aborting link thread, killing connection, releasing waiters and quiting"
|
||||
self.sock.close()
|
||||
try:cmd.waiter.release()
|
||||
except:pass
|
||||
for tag, cmd in self.cmd_queue.iteritems():
|
||||
try:cmd.waiter.release()
|
||||
except:pass
|
||||
sys.exit()
|
||||
|
||||
def _handle_timeouts(self):
|
||||
willpop = []
|
||||
for tag, cmd in self.cmd_queue.iteritems():
|
||||
if not tag:
|
||||
continue
|
||||
if time() - cmd.started > self.timeout:
|
||||
self.tags.remove(cmd.tag)
|
||||
willpop.append(cmd.tag)
|
||||
cmd.waiter.release()
|
||||
|
||||
for tag in willpop:
|
||||
self.cmd_queue.pop(tag)
|
||||
|
||||
def _resp_queue(self, response):
|
||||
if response.restag:
|
||||
self.resp_tagged_queue[response.restag] = response
|
||||
else:
|
||||
self.resp_untagged_queue.append(response)
|
||||
|
||||
def getresponse(self, command):
|
||||
if command:
|
||||
resp = self.resp_tagged_queue.pop(command.tag)
|
||||
else:
|
||||
resp = self.resp_untagged_queue.pop()
|
||||
self.tags.remove(resp.restag)
|
||||
return resp
|
||||
|
||||
def _cmd_queue(self, command):
|
||||
self.cmd_queue[command.tag] = command
|
||||
self.tags.append(command.tag)
|
||||
|
||||
def _cmd_dequeue(self, resp):
|
||||
if not resp.restag:
|
||||
return None
|
||||
else:
|
||||
return self.cmd_queue.pop(resp.restag)
|
||||
|
||||
def _delay(self):
|
||||
return (self.delay < 2.1 and 2.1 or self.delay)
|
||||
|
||||
def _do_delay(self):
|
||||
age = time() - self.lastpacket
|
||||
delay = self._delay()
|
||||
if age <= delay:
|
||||
sleep(delay - age)
|
||||
|
||||
def _send(self, command):
|
||||
if self.banned:
|
||||
self.log("NetIO | BANNED")
|
||||
raise AniDBBannedError, "Not sending, banned"
|
||||
self._do_delay()
|
||||
self.lastpacket = time()
|
||||
command.started = time()
|
||||
data = command.raw_data()
|
||||
|
||||
self.sock.sendto(data, self.target)
|
||||
if command.command == 'AUTH' and self.logPrivate:
|
||||
self.log("NetIO > sensitive data is not logged!")
|
||||
else:
|
||||
self.log("NetIO > %s" % repr(data))
|
||||
|
||||
def new_tag(self):
|
||||
if not len(self.tags):
|
||||
maxtag = "T000"
|
||||
else:
|
||||
maxtag = max(self.tags)
|
||||
newtag = "T%03d" % (int(maxtag[1:]) + 1)
|
||||
return newtag
|
||||
|
||||
def request(self, command):
|
||||
if not (self.session and command.session) and command.command not in ('AUTH', 'PING', 'ENCRYPT'):
|
||||
raise AniDBMustAuthError, "You must be authed to execute commands besides AUTH and PING"
|
||||
command.started = time()
|
||||
self._cmd_queue(command)
|
||||
self._send(command)
|
138
lib/adba/aniDBmaper.py
Normal file
138
lib/adba/aniDBmaper.py
Normal file
@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
from random import shuffle
|
||||
|
||||
class AniDBMaper:
|
||||
|
||||
blacklist = ('unused','retired','reserved')
|
||||
|
||||
def getAnimeBitsA(self,amask):
|
||||
map = self.getAnimeMapA()
|
||||
return self._getBitChain(map,amask);
|
||||
|
||||
def getAnimeCodesA(self,aBitChain):
|
||||
amap = self.getAnimeMapA()
|
||||
return self._getCodes(amap,aBitChain);
|
||||
|
||||
|
||||
def getFileBitsF(self,fmask):
|
||||
fmap = self.getFileMapF()
|
||||
return self._getBitChain(fmap,fmask)
|
||||
|
||||
def getFileCodesF(self,bitChainF):
|
||||
fmap = self.getFileMapF()
|
||||
return self._getCodes(fmap,bitChainF)
|
||||
|
||||
|
||||
def getFileBitsA(self,amask):
|
||||
amap = self.getFileMapA()
|
||||
return self._getBitChain(amap,amask)
|
||||
|
||||
def getFileCodesA(self,bitChainA):
|
||||
amap = self.getFileMapA()
|
||||
return self._getCodes(amap,bitChainA)
|
||||
|
||||
|
||||
def _getBitChain(self,map,wanted):
|
||||
"""Return an hex string with the correct bit set corresponding to the wanted fields in the map
|
||||
"""
|
||||
bit = 0
|
||||
for index,field in enumerate(map):
|
||||
if field in wanted and not field in self.blacklist:
|
||||
bit = bit ^ (1<<len(map)-index-1)
|
||||
|
||||
bit = str(hex(bit)).lstrip("0x").rstrip("L")
|
||||
bit = ''.join(["0" for unused in xrange(len(map)/4 - len(bit))])+bit
|
||||
return bit
|
||||
|
||||
def _getCodes(self,map,bitChain):
|
||||
"""Returns a list with the corresponding fields as set in the bitChain (hex string)
|
||||
"""
|
||||
codeList=[]
|
||||
bitChain = int(bitChain,16)
|
||||
mapLength = len(map)
|
||||
for i in reversed(range(mapLength)):
|
||||
if bitChain&(2**i):
|
||||
codeList.append(map[mapLength-i-1])
|
||||
return codeList
|
||||
|
||||
def getAnimeMapA(self):
|
||||
# each line is one byte
|
||||
# only chnage this if the api changes
|
||||
map = ['aid','unused','year','type','related_aid_list','related_aid_type','category_list','category_weight_list',
|
||||
'romaji_name','kanji_name','english_name','other_name','short_name_list','synonym_list','retired','retired',
|
||||
'episodes','highest_episode_number','special_ep_count','air_date','end_date','url','picname','category_id_list',
|
||||
'rating','vote_count','temp_rating','temp_vote_count','average_review_rating','review_count','award_list','is_18_restricted',
|
||||
'anime_planet_id','ANN_id','allcinema_id','AnimeNfo_id','unused','unused','unused','date_record_updated',
|
||||
'character_id_list','creator_id_list','main_creator_id_list','main_creator_name_list','unused','unused','unused','unused',
|
||||
'specials_count','credits_count','other_count','trailer_count','parody_count','unused','unused','unused']
|
||||
return map
|
||||
|
||||
def getFileMapF(self):
|
||||
# each line is one byte
|
||||
# only chnage this if the api changes
|
||||
map = ['unused','aid','eid','gid','mylist_id','list_other_episodes','IsDeprecated','state',
|
||||
'size','ed2k','md5','sha1','crc32','unused','unused','reserved',
|
||||
'quality','source','audio_codec_list','audio_bitrate_list','video_codec','video_bitrate','video_resolution','file_type_extension',
|
||||
'dub_language','sub_language','length_in_seconds','description','aired_date','unused','unused','anidb_file_name',
|
||||
'mylist_state','mylist_filestate','mylist_viewed','mylist_viewdate','mylist_storage','mylist_source','mylist_other','unused']
|
||||
return map
|
||||
|
||||
def getFileMapA(self):
|
||||
# each line is one byte
|
||||
# only chnage this if the api changes
|
||||
map = ['anime_total_episodes','highest_episode_number','year','type','related_aid_list','related_aid_type','category_list','reserved',
|
||||
'romaji_name','kanji_name','english_name','other_name','short_name_list','synonym_list','retired','retired',
|
||||
'epno','ep_name','ep_romaji_name','ep_kanji_name','episode_rating','episode_vote_count','unused','unused',
|
||||
'group_name','group_short_name','unused','unused','unused','unused','unused','date_aid_record_updated']
|
||||
return map
|
||||
|
||||
def checkMapping(self,verbos=False):
|
||||
|
||||
print "------"
|
||||
print "File F: "+ str(self.checkMapFileF(verbos))
|
||||
print "------"
|
||||
print "File A: "+ str(self.checkMapFileA(verbos))
|
||||
|
||||
|
||||
def checkMapFileF(self,verbos=False):
|
||||
getGeneralMap = lambda: self.getFileMapF()
|
||||
getBits = lambda x: self.getFileBitsF(x)
|
||||
getCodes = lambda x: self.getFileCodesF(x)
|
||||
return self._checkMapGeneral(getGeneralMap,getBits,getCodes,verbos=verbos)
|
||||
|
||||
def checkMapFileA(self,verbos=False):
|
||||
getGeneralMap = lambda: self.getFileMapA()
|
||||
getBits = lambda x: self.getFileBitsA(x)
|
||||
getCodes = lambda x: self.getFileCodesA(x)
|
||||
return self._checkMapGeneral(getGeneralMap,getBits,getCodes,verbos=verbos)
|
||||
|
||||
def _checkMapGeneral(self,getGeneralMap,getBits,getCodes,verbos=False):
|
||||
map = getGeneralMap()
|
||||
shuffle(map)
|
||||
mask = [elem for elem in map if elem not in self.blacklist][:5]
|
||||
bits = getBits(mask)
|
||||
mask_re = getCodes(bits)
|
||||
bits_re = getBits(mask_re)
|
||||
if verbos:
|
||||
print mask
|
||||
print mask_re
|
||||
print bits
|
||||
print bits_re
|
||||
print "bits are:"+ str((bits_re == bits))
|
||||
print "map is :"+ str((sorted(mask_re) == sorted(mask)))
|
||||
return (bits_re == bits) and sorted(mask_re) == sorted(mask)
|
1856
lib/adba/aniDBresponses.py
Normal file
1856
lib/adba/aniDBresponses.py
Normal file
File diff suppressed because it is too large
Load Diff
65
lib/adba/aniDBtvDBmaper.py
Normal file
65
lib/adba/aniDBtvDBmaper.py
Normal file
@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# This file is part of aDBa.
|
||||
#
|
||||
# aDBa is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# aDBa is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import xml.etree.cElementTree as etree
|
||||
import aniDBfileInfo as fileInfo
|
||||
|
||||
|
||||
|
||||
class TvDBMap():
|
||||
|
||||
def __init__(self,filePath=None):
|
||||
self.xmlMap = fileInfo.read_tvdb_map_xml(filePath)
|
||||
|
||||
def get_tvdb_for_anidb(self,anidb_id):
|
||||
return self._get_x_for_y(anidb_id,"anidbid","tvdbid")
|
||||
|
||||
def get_anidb_for_tvdb(self,tvdb_id):
|
||||
return self._get_x_for_y(tvdb_id,"tvdbid","anidbid")
|
||||
|
||||
|
||||
def _get_x_for_y(self,xValue,x,y):
|
||||
#print("searching "+x+" with the value "+str(xValue)+" and want to give back "+y)
|
||||
xValue = str(xValue)
|
||||
for anime in self.xmlMap.findall("anime"):
|
||||
try:
|
||||
if anime.get(x,False) == xValue:
|
||||
return int(anime.get(y,0))
|
||||
except ValueError, e:
|
||||
continue
|
||||
return 0
|
||||
|
||||
|
||||
def get_season_episode_for_anidb_absoluteNumber(self,anidb_id,absoluteNumber):
|
||||
# NOTE: this cant be done without the length of each season from thetvdb
|
||||
#TODO: implement
|
||||
season = 0
|
||||
episode = 0
|
||||
|
||||
for anime in self.xmlMap.findall("anime"):
|
||||
if int(anime.get("anidbid",False)) == anidb_id:
|
||||
defaultSeason = int(anime.get("defaulttvdbseason",1))
|
||||
|
||||
|
||||
return (season,episode)
|
||||
|
||||
def get_season_episode_for_tvdb_absoluteNumber(self,anidb_id,absoluteNumber):
|
||||
#TODO: implement
|
||||
season = 0
|
||||
episode = 0
|
||||
return (season,episode)
|
5250
lib/adba/anime-list.xml
Normal file
5250
lib/adba/anime-list.xml
Normal file
File diff suppressed because it is too large
Load Diff
49439
lib/adba/animetitles.xml
Normal file
49439
lib/adba/animetitles.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -418,7 +418,7 @@ class TVRage:
|
||||
'airtime': 'airs_time',
|
||||
'airday': 'airs_dayofweek',
|
||||
'image': 'fanart',
|
||||
'epnum': 'id',
|
||||
'epnum': 'absolute_number',
|
||||
'title': 'episodename',
|
||||
'airdate': 'firstaired',
|
||||
'screencap': 'filename',
|
||||
|
@ -32,7 +32,7 @@ from threading import Lock
|
||||
from sickbeard import providers, metadata, config
|
||||
from sickbeard.providers.generic import GenericProvider
|
||||
from providers import ezrss, tvtorrents, btn, newznab, womble, thepiratebay, torrentleech, kat, publichd, iptorrents, \
|
||||
omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, nextgen, speedcd
|
||||
omgwtfnzbs, scc, hdtorrents, torrentday, hdbits, nextgen, speedcd, nyaatorrents, fanzub
|
||||
from sickbeard.config import CheckSection, check_setting_int, check_setting_str, check_setting_float, ConfigMigrator, \
|
||||
naming_ep_type
|
||||
from sickbeard import searchBacklog, showUpdater, versionChecker, properFinder, autoPostProcesser, \
|
||||
@ -159,6 +159,7 @@ FLATTEN_FOLDERS_DEFAULT = None
|
||||
SUBTITLES_DEFAULT = None
|
||||
INDEXER_DEFAULT = None
|
||||
INDEXER_TIMEOUT = None
|
||||
ANIME_DEFAULT = None
|
||||
PROVIDER_ORDER = []
|
||||
|
||||
NAMING_MULTI_EP = None
|
||||
@ -169,6 +170,7 @@ NAMING_SPORTS_PATTERN = None
|
||||
NAMING_CUSTOM_SPORTS = None
|
||||
NAMING_FORCE_FOLDERS = False
|
||||
NAMING_STRIP_YEAR = None
|
||||
NAMING_ANIME = None
|
||||
|
||||
USE_NZBS = None
|
||||
USE_TORRENTS = None
|
||||
@ -320,6 +322,14 @@ NMJ_HOST = None
|
||||
NMJ_DATABASE = None
|
||||
NMJ_MOUNT = None
|
||||
|
||||
ANIMESUPPORT = False
|
||||
USE_ANIDB = False
|
||||
ANIDB_USERNAME = None
|
||||
ANIDB_PASSWORD = None
|
||||
ANIDB_USE_MYLIST = 0
|
||||
ADBA_CONNECTION = None
|
||||
ANIME_SPLIT_HOME = False
|
||||
|
||||
USE_SYNOINDEX = False
|
||||
|
||||
USE_NMJv2 = False
|
||||
@ -460,7 +470,9 @@ def initialize(consoleLogging=True):
|
||||
METADATA_WDTV, METADATA_TIVO, METADATA_MEDE8ER, IGNORE_WORDS, CALENDAR_UNPROTECTED, CREATE_MISSING_SHOW_DIRS, \
|
||||
ADD_SHOWS_WO_DIR, USE_SUBTITLES, SUBTITLES_LANGUAGES, SUBTITLES_DIR, SUBTITLES_SERVICES_LIST, SUBTITLES_SERVICES_ENABLED, SUBTITLES_HISTORY, SUBTITLES_FINDER_FREQUENCY, subtitlesFinderScheduler, \
|
||||
USE_FAILED_DOWNLOADS, DELETE_FAILED, ANON_REDIRECT, LOCALHOST_IP, TMDB_API_KEY, DEBUG, PROXY_SETTING, \
|
||||
AUTOPOSTPROCESSER_FREQUENCY, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY
|
||||
AUTOPOSTPROCESSER_FREQUENCY, DEFAULT_AUTOPOSTPROCESSER_FREQUENCY, MIN_AUTOPOSTPROCESSER_FREQUENCY, \
|
||||
ANIME_DEFAULT, NAMING_ANIME, ANIMESUPPORT, USE_ANIDB, ANIDB_USERNAME, ANIDB_PASSWORD, ANIDB_USE_MYLIST, \
|
||||
ANIME_SPLIT_HOME
|
||||
|
||||
if __INITIALIZED__:
|
||||
return False
|
||||
@ -572,6 +584,7 @@ def initialize(consoleLogging=True):
|
||||
FLATTEN_FOLDERS_DEFAULT = bool(check_setting_int(CFG, 'General', 'flatten_folders_default', 0))
|
||||
INDEXER_DEFAULT = check_setting_int(CFG, 'General', 'indexer_default', 0)
|
||||
INDEXER_TIMEOUT = check_setting_int(CFG, 'General', 'indexer_timeout', 10)
|
||||
ANIME_DEFAULT = bool(check_setting_int(CFG, 'General', 'anime_default', 0))
|
||||
|
||||
PROVIDER_ORDER = check_setting_str(CFG, 'General', 'provider_order', '').split()
|
||||
|
||||
@ -584,6 +597,7 @@ def initialize(consoleLogging=True):
|
||||
NAMING_MULTI_EP = check_setting_int(CFG, 'General', 'naming_multi_ep', 1)
|
||||
NAMING_FORCE_FOLDERS = naming.check_force_season_folders()
|
||||
NAMING_STRIP_YEAR = bool(check_setting_int(CFG, 'General', 'naming_strip_year', 0))
|
||||
NAMING_ANIME = check_setting_int(CFG, 'General', 'naming_anime', 3)
|
||||
|
||||
USE_NZBS = bool(check_setting_int(CFG, 'General', 'use_nzbs', 0))
|
||||
USE_TORRENTS = bool(check_setting_int(CFG, 'General', 'use_torrents', 1))
|
||||
@ -842,6 +856,13 @@ def initialize(consoleLogging=True):
|
||||
|
||||
USE_LISTVIEW = bool(check_setting_int(CFG, 'General', 'use_listview', 0))
|
||||
|
||||
ANIMESUPPORT = False
|
||||
USE_ANIDB = check_setting_str(CFG, 'ANIDB', 'use_anidb', '')
|
||||
ANIDB_USERNAME = check_setting_str(CFG, 'ANIDB', 'anidb_username', '')
|
||||
ANIDB_PASSWORD = check_setting_str(CFG, 'ANIDB', 'anidb_password', '')
|
||||
ANIDB_USE_MYLIST = check_setting_str(CFG, 'ANIDB', 'anidb_use_mylist', '')
|
||||
ANIME_SPLIT_HOME = bool(check_setting_int(CFG, 'ANIME', 'anime_split_home', 0))
|
||||
|
||||
METADATA_XBMC = check_setting_str(CFG, 'General', 'metadata_xbmc', '0|0|0|0|0|0|0|0|0|0')
|
||||
METADATA_XBMC_12PLUS = check_setting_str(CFG, 'General', 'metadata_xbmc_12plus', '0|0|0|0|0|0|0|0|0|0')
|
||||
METADATA_MEDIABROWSER = check_setting_str(CFG, 'General', 'metadata_mediabrowser', '0|0|0|0|0|0|0|0|0|0')
|
||||
@ -1211,6 +1232,15 @@ def halt():
|
||||
except:
|
||||
pass
|
||||
|
||||
if ADBA_CONNECTION:
|
||||
ADBA_CONNECTION.logout()
|
||||
#ADBA_CONNECTION.stop()
|
||||
logger.log(u"Waiting for the ANIDB CONNECTION thread to exit")
|
||||
try:
|
||||
ADBA_CONNECTION.join(5)
|
||||
except:
|
||||
pass
|
||||
|
||||
__INITIALIZED__ = False
|
||||
|
||||
|
||||
@ -1362,6 +1392,7 @@ def save_config():
|
||||
new_config['General']['flatten_folders_default'] = int(FLATTEN_FOLDERS_DEFAULT)
|
||||
new_config['General']['indexer_default'] = int(INDEXER_DEFAULT)
|
||||
new_config['General']['indexer_timeout'] = int(INDEXER_TIMEOUT)
|
||||
new_config['General']['anime_default'] = int(ANIME_DEFAULT)
|
||||
new_config['General']['provider_order'] = ' '.join(PROVIDER_ORDER)
|
||||
new_config['General']['version_notify'] = int(VERSION_NOTIFY)
|
||||
new_config['General']['auto_update'] = int(AUTO_UPDATE)
|
||||
@ -1372,6 +1403,7 @@ def save_config():
|
||||
new_config['General']['naming_custom_sports'] = int(NAMING_CUSTOM_SPORTS)
|
||||
new_config['General']['naming_sports_pattern'] = NAMING_SPORTS_PATTERN
|
||||
new_config['General']['naming_multi_ep'] = int(NAMING_MULTI_EP)
|
||||
new_config['General']['naming_anime'] = int(NAMING_ANIME)
|
||||
new_config['General']['launch_browser'] = int(LAUNCH_BROWSER)
|
||||
new_config['General']['update_shows_on_start'] = int(UPDATE_SHOWS_ON_START)
|
||||
new_config['General']['sort_article'] = int(SORT_ARTICLE)
|
||||
@ -1711,6 +1743,15 @@ def save_config():
|
||||
new_config['FailedDownloads']['use_failed_downloads'] = int(USE_FAILED_DOWNLOADS)
|
||||
new_config['FailedDownloads']['delete_failed'] = int(DELETE_FAILED)
|
||||
|
||||
new_config['ANIDB'] = {}
|
||||
new_config['ANIDB']['use_anidb'] = USE_ANIDB
|
||||
new_config['ANIDB']['anidb_username'] = ANIDB_USERNAME
|
||||
new_config['ANIDB']['anidb_password'] = helpers.encrypt(ANIDB_PASSWORD, ENCRYPTION_VERSION)
|
||||
new_config['ANIDB']['anidb_use_mylist'] = ANIDB_USE_MYLIST
|
||||
|
||||
new_config['ANIME'] = {}
|
||||
new_config['ANIME']['anime_split_home'] = int(ANIME_SPLIT_HOME)
|
||||
|
||||
new_config.write()
|
||||
|
||||
|
||||
|
@ -150,7 +150,7 @@ class Quality:
|
||||
return (sorted(anyQualities), sorted(bestQualities))
|
||||
|
||||
@staticmethod
|
||||
def nameQuality(name):
|
||||
def nameQuality(name, anime=False):
|
||||
"""
|
||||
Return The quality from an episode File renamed by Sickbeard
|
||||
If no quality is achieved it will try sceneQuality regex
|
||||
@ -162,8 +162,9 @@ class Quality:
|
||||
for x in sorted(Quality.qualityStrings.keys(), reverse=True):
|
||||
if x == Quality.UNKNOWN:
|
||||
continue
|
||||
|
||||
if x == Quality.NONE: #Last chance
|
||||
return Quality.sceneQuality(name)
|
||||
return Quality.sceneQuality(name, anime)
|
||||
|
||||
regex = '\W' + Quality.qualityStrings[x].replace(' ', '\W') + '\W'
|
||||
regex_match = re.search(regex, name, re.I)
|
||||
@ -171,7 +172,7 @@ class Quality:
|
||||
return x
|
||||
|
||||
@staticmethod
|
||||
def sceneQuality(name):
|
||||
def sceneQuality(name, anime=False):
|
||||
"""
|
||||
Return The quality from the scene episode File
|
||||
"""
|
||||
@ -180,6 +181,26 @@ class Quality:
|
||||
|
||||
checkName = lambda list, func: func([re.search(x, name, re.I) for x in list])
|
||||
|
||||
if anime:
|
||||
blueRayOptions = checkName(["bluray", "blu-ray"], any)
|
||||
hdOptions = checkName(["720p", "1280x720", "960x720"], any)
|
||||
fullHD = checkName(["1080p", "1920x1080"], any)
|
||||
|
||||
if checkName(["360p", "XviD"], any):
|
||||
return Quality.SDTV
|
||||
elif checkName(["dvd", "480p", "848x480"], any):
|
||||
return Quality.SDDVD
|
||||
elif hdOptions and not blueRayOptions and not fullHD:
|
||||
return Quality.HDTV
|
||||
elif hdOptions and not blueRayOptions and not fullHD:
|
||||
return Quality.HDWEBDL
|
||||
elif blueRayOptions and hdOptions and not fullHD:
|
||||
return Quality.HDBLURAY
|
||||
elif fullHD:
|
||||
return Quality.FULLHDBLURAY
|
||||
else:
|
||||
return Quality.UNKNOWN
|
||||
|
||||
if checkName(["(pdtv|hdtv|dsr|tvrip).(xvid|x264|h.?264)"], all) and not checkName(["(720|1080)[pi]"], all):
|
||||
return Quality.SDTV
|
||||
elif checkName(["web.dl|webrip", "xvid|x264|h.?264"], all) and not checkName(["(720|1080)[pi]"], all):
|
||||
|
@ -586,7 +586,7 @@ class ConfigMigrator():
|
||||
logger.ERROR)
|
||||
continue
|
||||
|
||||
if name == 'SickRage Index':
|
||||
if name == 'Sick Beard Index':
|
||||
key = '0'
|
||||
|
||||
if name == 'NZBs.org':
|
||||
|
@ -44,7 +44,7 @@ class AddSceneExceptions(InitialSchema):
|
||||
|
||||
def execute(self):
|
||||
self.connection.action(
|
||||
"CREATE TABLE scene_exceptions (exception_id INTEGER PRIMARY KEY, tvdb_id INTEGER KEY, show_name TEXT)")
|
||||
"CREATE TABLE scene_exceptions (exception_id INTEGER PRIMARY KEY, indexer_id INTEGER KEY, show_name TEXT)")
|
||||
|
||||
|
||||
class AddSceneNameCache(AddSceneExceptions):
|
||||
@ -52,7 +52,7 @@ class AddSceneNameCache(AddSceneExceptions):
|
||||
return self.hasTable("scene_names")
|
||||
|
||||
def execute(self):
|
||||
self.connection.action("CREATE TABLE scene_names (tvdb_id INTEGER, name TEXT)")
|
||||
self.connection.action("CREATE TABLE scene_names (indexer_id INTEGER, name TEXT)")
|
||||
|
||||
|
||||
class AddNetworkTimezones(AddSceneNameCache):
|
||||
@ -69,7 +69,7 @@ class AddXemNumbering(AddNetworkTimezones):
|
||||
|
||||
def execute(self):
|
||||
self.connection.action(
|
||||
"CREATE TABLE xem_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, scene_season INTEGER, scene_episode INTEGER)")
|
||||
"CREATE TABLE xem_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, absolute_number INTEGER, scene_season INTEGER, scene_episode INTEGER, scene_absolute_number INTEGER, PRIMARY KEY (indexer_id, scene_season, scene_episode))")
|
||||
|
||||
class AddXemRefresh(AddXemNumbering):
|
||||
def test(self):
|
||||
@ -79,52 +79,24 @@ class AddXemRefresh(AddXemNumbering):
|
||||
self.connection.action(
|
||||
"CREATE TABLE xem_refresh (indexer TEXT, indexer_id INTEGER PRIMARY KEY, last_refreshed INTEGER)")
|
||||
|
||||
class ConvertSceneExceptionsToIndexerID(AddXemRefresh):
|
||||
def test(self):
|
||||
return self.hasColumn("scene_exceptions", "indexer_id")
|
||||
|
||||
def execute(self):
|
||||
if self.hasTable("tmp_scene_exceptions"):
|
||||
self.connection.action("DROP TABLE tmp_scene_exceptions")
|
||||
|
||||
self.connection.action("ALTER TABLE scene_exceptions RENAME TO tmp_scene_exceptions")
|
||||
self.connection.action(
|
||||
"CREATE TABLE scene_exceptions (exception_id INTEGER PRIMARY KEY, indexer_id INTEGER KEY, show_name TEXT)")
|
||||
self.connection.action(
|
||||
"INSERT INTO scene_exceptions(exception_id, indexer_id, show_name) SELECT exception_id, tvdb_id, show_name FROM tmp_scene_exceptions")
|
||||
self.connection.action("DROP TABLE tmp_scene_exceptions")
|
||||
|
||||
|
||||
class ConvertSceneNamesToIndexerID(ConvertSceneExceptionsToIndexerID):
|
||||
def test(self):
|
||||
return self.hasColumn("scene_names", "indexer_id")
|
||||
|
||||
def execute(self):
|
||||
if self.hasTable("tmp_scene_names"):
|
||||
self.connection.action("DROP TABLE tmp_scene_names")
|
||||
|
||||
self.connection.action("ALTER TABLE scene_names RENAME TO tmp_scene_names")
|
||||
self.connection.action("CREATE TABLE scene_names (indexer_id INTEGER, name TEXT)")
|
||||
self.connection.action("INSERT INTO scene_names(indexer_id, name) SELECT tvdb_id, name FROM tmp_scene_names")
|
||||
self.connection.action("DROP TABLE tmp_scene_names")
|
||||
|
||||
class ConvertIndexerToInteger(ConvertSceneNamesToIndexerID):
|
||||
def execute(self):
|
||||
ql = []
|
||||
ql.append(["UPDATE xem_numbering SET indexer = ? WHERE LOWER(indexer) = ?", ["1", "tvdb"]])
|
||||
ql.append(["UPDATE xem_numbering SET indexer = ? WHERE LOWER(indexer) = ?", ["2", "tvrage"]])
|
||||
ql.append(["UPDATE xem_refresh SET indexer = ? WHERE LOWER(indexer) = ?", ["1", "tvdb"]])
|
||||
ql.append(["UPDATE xem_refresh SET indexer = ? WHERE LOWER(indexer) = ?", ["2", "tvrage"]])
|
||||
self.connection.mass_action(ql)
|
||||
|
||||
class RemoveKeysFromXemNumbering(ConvertIndexerToInteger):
|
||||
def execute(self):
|
||||
self.connection.action("ALTER TABLE xem_numbering DROP UNIQUE (indexer, indexer_id, season, episode)")
|
||||
self.connection.action("ALTER TABLE xem_numbering DROP PRIMARY KEY")
|
||||
|
||||
class AddLastSearch(RemoveKeysFromXemNumbering):
|
||||
class AddLastSearch(AddXemRefresh):
|
||||
def test(self):
|
||||
return self.hasTable("lastSearch")
|
||||
|
||||
def execute(self):
|
||||
self.connection.action("CREATE TABLE lastSearch (provider TEXT, time NUMERIC)")
|
||||
|
||||
class AddAbsoluteNumbering(AddLastSearch):
|
||||
def test(self):
|
||||
return self.hasColumn("xem_numbering", "absolute_number")
|
||||
|
||||
def execute(self):
|
||||
self.addColumn("xem_numbering", "absolute_number", "NUMERIC", "0")
|
||||
self.addColumn("xem_numbering", "scene_absolute_number", "NUMERIC", "0")
|
||||
|
||||
class AddSceneExceptionsSeasons(AddSceneNameCache):
|
||||
def test(self):
|
||||
return self.hasColumn("scene_exceptions", "season")
|
||||
|
||||
def execute(self):
|
||||
self.addColumn("scene_exceptions", "season", "NUMERIC", -1)
|
@ -27,8 +27,7 @@ from sickbeard import encodingKludge as ek
|
||||
from sickbeard.name_parser.parser import NameParser, InvalidNameException
|
||||
|
||||
MIN_DB_VERSION = 9 # oldest db version we support migrating from
|
||||
MAX_DB_VERSION = 31
|
||||
|
||||
MAX_DB_VERSION = 34
|
||||
|
||||
class MainSanityCheck(db.DBSanityCheck):
|
||||
def check(self):
|
||||
@ -714,7 +713,7 @@ class AddSceneNumbering(AddArchiveFirstMatchOption):
|
||||
self.connection.action("DROP TABLE scene_numbering")
|
||||
|
||||
self.connection.action(
|
||||
"CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, scene_season INTEGER, scene_episode INTEGER, PRIMARY KEY (indexer_id, season, episode))")
|
||||
"CREATE TABLE scene_numbering (indexer TEXT, indexer_id INTEGER, season INTEGER, episode INTEGER, scene_season INTEGER, scene_episode INTEGER, PRIMARY KEY (indexer_id, season, episode, scene_season, scene_episode))")
|
||||
|
||||
self.incDBVersion()
|
||||
|
||||
@ -794,10 +793,44 @@ class AddSceneNumberingToTvEpisodes(AddSportsOption):
|
||||
backupDatabase(31)
|
||||
|
||||
logger.log(u"Adding column scene_season and scene_episode to tvepisodes")
|
||||
if not self.hasColumn("tv_episodes", "scene_season"):
|
||||
self.addColumn("tv_episodes", "scene_season", "NUMERIC", -1)
|
||||
self.addColumn("tv_episodes", "scene_season", "NUMERIC", "NULL")
|
||||
self.addColumn("tv_episodes", "scene_episode", "NUMERIC", "NULL")
|
||||
|
||||
if not self.hasColumn("tv_episodes", "scene_episode"):
|
||||
self.addColumn("tv_episodes", "scene_episode", "NUMERIC", -1)
|
||||
self.incDBVersion()
|
||||
|
||||
self.incDBVersion()
|
||||
class AddAnimeTVShow(AddSceneNumberingToTvEpisodes):
|
||||
def test(self):
|
||||
return self.checkDBVersion() >= 32
|
||||
|
||||
def execute(self):
|
||||
backupDatabase(32)
|
||||
|
||||
logger.log(u"Adding column anime to tv_episodes")
|
||||
self.addColumn("tv_shows", "anime", "NUMERIC", "0")
|
||||
|
||||
self.incDBVersion()
|
||||
|
||||
class AddAbsoluteNumbering(AddAnimeTVShow):
|
||||
def test(self):
|
||||
return self.checkDBVersion() >= 33
|
||||
|
||||
def execute(self):
|
||||
backupDatabase(33)
|
||||
|
||||
logger.log(u"Adding column absolute_number to tv_episodes")
|
||||
self.addColumn("tv_episodes", "absolute_number", "NUMERIC", "0")
|
||||
|
||||
self.incDBVersion()
|
||||
|
||||
class AddSceneAbsoluteNumbering(AddAbsoluteNumbering):
|
||||
def test(self):
|
||||
return self.checkDBVersion() >= 34
|
||||
|
||||
def execute(self):
|
||||
backupDatabase(34)
|
||||
|
||||
logger.log(u"Adding column absolute_number and scene_absolute_number to scene_numbering")
|
||||
self.addColumn("scene_numbering", "absolute_number", "NUMERIC", "0")
|
||||
self.addColumn("scene_numbering", "scene_absolute_number", "NUMERIC", "0")
|
||||
|
||||
self.incDBVersion()
|
||||
|
@ -120,8 +120,13 @@ class DBConnection:
|
||||
sqlResult = []
|
||||
attempt = 0
|
||||
|
||||
# Transaction
|
||||
self.connection.isolation_level = None
|
||||
self.connection.execute('begin')
|
||||
|
||||
while attempt < 5:
|
||||
try:
|
||||
|
||||
for qu in querylist:
|
||||
if len(qu) == 1:
|
||||
if logTransaction:
|
||||
@ -131,7 +136,9 @@ class DBConnection:
|
||||
if logTransaction:
|
||||
logger.log(qu[0] + " with args " + str(qu[1]), logger.DEBUG)
|
||||
sqlResult.append(self.connection.execute(qu[0], qu[1]))
|
||||
self.connection.commit()
|
||||
|
||||
self.connection.execute('commit')
|
||||
|
||||
logger.log(u"Transaction with " + str(len(querylist)) + u" queries executed", logger.DEBUG)
|
||||
return sqlResult
|
||||
except sqlite3.OperationalError, e:
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
from sickbeard.encodingKludge import fixStupidEncodings
|
||||
|
||||
|
||||
def ex(e):
|
||||
"""
|
||||
Returns a unicode string from the exception text if it exists.
|
||||
@ -136,4 +137,8 @@ class FailedHistoryMultiSnatchException(SickBeardException):
|
||||
|
||||
|
||||
class FailedHistoryNotFoundException(SickBeardException):
|
||||
"The release was not found in the failed download history tracker"
|
||||
"The release was not found in the failed download history tracker"
|
||||
|
||||
|
||||
class EpisodeNotFoundByAbsoluteNumberException(SickBeardException):
|
||||
"The show wasn't found in the DB while looking at Absolute Numbers"
|
||||
|
@ -50,13 +50,14 @@ except ImportError:
|
||||
from xml.dom.minidom import Node
|
||||
|
||||
import sickbeard
|
||||
from sickbeard.exceptions import MultipleShowObjectsException, ex
|
||||
from sickbeard.exceptions import MultipleShowObjectsException, EpisodeNotFoundByAbsoluteNumberException, ex
|
||||
from sickbeard import logger, classes
|
||||
from sickbeard.common import USER_AGENT, mediaExtensions, subtitleExtensions, XML_NSMAP
|
||||
from sickbeard import db
|
||||
from sickbeard import encodingKludge as ek
|
||||
from sickbeard import notifiers
|
||||
from lib import subliminal
|
||||
from lib import adba
|
||||
|
||||
urllib._urlopener = classes.SickBeardURLopener()
|
||||
|
||||
@ -186,8 +187,7 @@ def getURL(url, post_data=None, headers=None, params=None, timeout=30, json=Fals
|
||||
url = urlparse.urlunparse(parsed)
|
||||
|
||||
it = iter(req_headers)
|
||||
|
||||
|
||||
|
||||
if use_proxy and sickbeard.PROXY_SETTING:
|
||||
logger.log("Using proxy for url: " + url, logger.DEBUG)
|
||||
proxies = {
|
||||
@ -317,13 +317,15 @@ def searchDBForShow(regShowName, log=False):
|
||||
continue
|
||||
elif len(sqlResults) > 1:
|
||||
if log:
|
||||
logger.log(u"Multiple results for " + showName + " in the DB, unable to match show name", logger.DEBUG)
|
||||
logger.log(u"Multiple results for " + showName + " in the DB, unable to match show name",
|
||||
logger.DEBUG)
|
||||
continue
|
||||
else:
|
||||
return (int(sqlResults[0]["indexer_id"]), sqlResults[0]["show_name"])
|
||||
|
||||
return
|
||||
|
||||
|
||||
def searchIndexerForShowID(regShowName, indexer=None, indexer_id=None, ui=None):
|
||||
showNames = list(set([re.sub('[. -]', ' ', regShowName), regShowName]))
|
||||
|
||||
@ -337,11 +339,16 @@ def searchIndexerForShowID(regShowName, indexer=None, indexer_id=None, ui=None):
|
||||
for name in showNames:
|
||||
logger.log(u"Trying to find " + name + " on " + sickbeard.indexerApi(i).name, logger.DEBUG)
|
||||
|
||||
search = t[indexer_id] if indexer_id else t[name]
|
||||
try:
|
||||
search = t[indexer_id] if indexer_id else t[name]
|
||||
except:
|
||||
continue
|
||||
|
||||
try:
|
||||
seriesname = search.seriesname
|
||||
except:
|
||||
seriesname = None
|
||||
|
||||
try:
|
||||
series_id = search.id
|
||||
except:
|
||||
@ -360,6 +367,7 @@ def searchIndexerForShowID(regShowName, indexer=None, indexer_id=None, ui=None):
|
||||
|
||||
return (None, None, None)
|
||||
|
||||
|
||||
def sizeof_fmt(num):
|
||||
'''
|
||||
>>> sizeof_fmt(2)
|
||||
@ -508,7 +516,7 @@ def rename_ep_file(cur_path, new_path, old_path_length=0):
|
||||
old_path_length: The length of media file path (old name) WITHOUT THE EXTENSION
|
||||
"""
|
||||
|
||||
new_dest_dir, new_dest_name = os.path.split(new_path) #@UnusedVariable
|
||||
new_dest_dir, new_dest_name = os.path.split(new_path) # @UnusedVariable
|
||||
|
||||
if old_path_length == 0 or old_path_length > len(cur_path):
|
||||
# approach from the right
|
||||
@ -519,7 +527,7 @@ def rename_ep_file(cur_path, new_path, old_path_length=0):
|
||||
cur_file_name = cur_path[:old_path_length]
|
||||
|
||||
if cur_file_ext[1:] in subtitleExtensions:
|
||||
#Extract subtitle language from filename
|
||||
# Extract subtitle language from filename
|
||||
sublang = os.path.splitext(cur_file_name)[1][1:]
|
||||
|
||||
#Check if the language extracted from filename is a valid language
|
||||
@ -662,6 +670,41 @@ def fixSetGroupID(childPath):
|
||||
childPath, parentGID), logger.ERROR)
|
||||
|
||||
|
||||
def is_anime_in_show_list():
|
||||
for show in sickbeard.showList:
|
||||
if show.is_anime:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def update_anime_support():
|
||||
sickbeard.ANIMESUPPORT = is_anime_in_show_list()
|
||||
|
||||
|
||||
def get_all_episodes_from_absolute_number(show, indexer_id, absolute_numbers):
|
||||
if len(absolute_numbers) == 0:
|
||||
raise EpisodeNotFoundByAbsoluteNumberException()
|
||||
|
||||
episodes = []
|
||||
season = None
|
||||
|
||||
if not show and not indexer_id:
|
||||
return (season, episodes)
|
||||
|
||||
if not show and indexer_id:
|
||||
show = findCertainShow(sickbeard.showList, indexer_id)
|
||||
|
||||
for absolute_number in absolute_numbers:
|
||||
ep = show.getEpisode(None, None, absolute_number=absolute_number)
|
||||
if ep:
|
||||
episodes.append(ep.episode)
|
||||
else:
|
||||
raise EpisodeNotFoundByAbsoluteNumberException()
|
||||
season = ep.season # this will always take the last found seson so eps that cross the season border are not handeled well
|
||||
|
||||
return (season, episodes)
|
||||
|
||||
|
||||
def sanitizeSceneName(name, ezrss=False):
|
||||
"""
|
||||
Takes a show name and returns the "scenified" version of it.
|
||||
@ -947,11 +990,13 @@ def full_sanitizeSceneName(name):
|
||||
return re.sub('[. -]', ' ', sanitizeSceneName(name)).lower().lstrip()
|
||||
|
||||
|
||||
def _check_against_names(name, show):
|
||||
nameInQuestion = full_sanitizeSceneName(name)
|
||||
def _check_against_names(nameInQuestion, show, season=-1):
|
||||
|
||||
showNames = [show.name]
|
||||
showNames.extend(sickbeard.scene_exceptions.get_scene_exceptions(show.indexerid))
|
||||
showNames = []
|
||||
if season in [-1, 1]:
|
||||
showNames = [show.name]
|
||||
|
||||
showNames.extend(sickbeard.scene_exceptions.get_scene_exceptions(show.indexerid, season=season))
|
||||
|
||||
for showName in showNames:
|
||||
nameFromList = full_sanitizeSceneName(showName)
|
||||
@ -961,19 +1006,28 @@ def _check_against_names(name, show):
|
||||
return False
|
||||
|
||||
|
||||
def get_show_by_name(name):
|
||||
def get_show_by_name(name, useIndexer=False):
|
||||
name = full_sanitizeSceneName(name)
|
||||
|
||||
showObj = sickbeard.name_cache.retrieveShowFromCache(name)
|
||||
if not showObj:
|
||||
if not showObj and sickbeard.showList:
|
||||
showNames = list(set(sickbeard.show_name_helpers.sceneToNormalShowNames(name)))
|
||||
for showName in showNames if sickbeard.showList else []:
|
||||
sceneResults = [x for x in sickbeard.showList if _check_against_names(showName, x)]
|
||||
showObj = sceneResults[0] if len(sceneResults) else None
|
||||
if showObj:
|
||||
break
|
||||
for showName in showNames:
|
||||
if showName in sickbeard.scene_exceptions.exceptionIndexerCache:
|
||||
showObj = findCertainShow(sickbeard.showList, int(sickbeard.scene_exceptions.exceptionIndexerCache[showName]))
|
||||
if showObj:
|
||||
break
|
||||
|
||||
if useIndexer and not showObj:
|
||||
(sn, idx, id) = searchIndexerForShowID(showName, ui=classes.ShowListUI)
|
||||
if id:
|
||||
showObj = findCertainShow(sickbeard.showList, int(id))
|
||||
if showObj:
|
||||
break
|
||||
|
||||
return showObj
|
||||
|
||||
|
||||
def is_hidden_folder(folder):
|
||||
"""
|
||||
Returns True if folder is hidden.
|
||||
@ -986,6 +1040,7 @@ def is_hidden_folder(folder):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def real_path(path):
|
||||
"""
|
||||
Returns: the canonicalized absolute pathname. The resulting path will have no symbolic link, '/./' or '/../' components.
|
||||
@ -1009,3 +1064,28 @@ def validateShow(show, season=None, episode=None):
|
||||
return t[show.indexerid][season][episode]
|
||||
except (sickbeard.indexer_episodenotfound, sickbeard.indexer_seasonnotfound):
|
||||
pass
|
||||
|
||||
|
||||
def set_up_anidb_connection():
|
||||
if not sickbeard.USE_ANIDB:
|
||||
logger.log(u"Usage of anidb disabled. Skiping", logger.DEBUG)
|
||||
return False
|
||||
|
||||
if not sickbeard.ANIDB_USERNAME and not sickbeard.ANIDB_PASSWORD:
|
||||
logger.log(u"anidb username and/or password are not set. Aborting anidb lookup.", logger.DEBUG)
|
||||
return False
|
||||
|
||||
if not sickbeard.ADBA_CONNECTION:
|
||||
anidb_logger = lambda x: logger.log("ANIDB: " + str(x), logger.DEBUG)
|
||||
sickbeard.ADBA_CONNECTION = adba.Connection(keepAlive=True, log=anidb_logger)
|
||||
|
||||
if not sickbeard.ADBA_CONNECTION.authed():
|
||||
try:
|
||||
sickbeard.ADBA_CONNECTION.auth(sickbeard.ANIDB_USERNAME, sickbeard.ANIDB_PASSWORD)
|
||||
except Exception, e:
|
||||
logger.log(u"exception msg: " + str(e))
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
return sickbeard.ADBA_CONNECTION.authed()
|
@ -11,7 +11,7 @@
|
||||
# SickRage is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with SickRage. If not, see <http://www.gnu.org/licenses/>.
|
||||
@ -25,7 +25,7 @@ import time
|
||||
import sickbeard
|
||||
|
||||
from sickbeard import logger, helpers, scene_numbering
|
||||
from sickbeard.common import cpu_presets
|
||||
from sickbeard.exceptions import EpisodeNotFoundByAbsoluteNumberException
|
||||
from dateutil import parser
|
||||
|
||||
nameparser_lock = threading.Lock()
|
||||
@ -35,13 +35,24 @@ class NameParser(object):
|
||||
ALL_REGEX = 0
|
||||
NORMAL_REGEX = 1
|
||||
SPORTS_REGEX = 2
|
||||
ANIME_REGEX = 3
|
||||
|
||||
def __init__(self, file_name=True, regexMode=0):
|
||||
def __init__(self, file_name=True, show=None, useIndexers=False):
|
||||
|
||||
regexMode = self.ALL_REGEX
|
||||
if show and show.is_anime:
|
||||
regexMode = self.ANIME_REGEX
|
||||
elif show and show.is_sports:
|
||||
regexMode = self.SPORTS_REGEX
|
||||
elif show and not show.is_anime and not show.is_sports:
|
||||
regexMode = self.NORMAL_REGEX
|
||||
|
||||
self.file_name = file_name
|
||||
self.regexMode = regexMode
|
||||
self.compiled_regexes = []
|
||||
self.compiled_regexes = {}
|
||||
self._compile_regexes(self.regexMode)
|
||||
self.showList = sickbeard.showList
|
||||
self.useIndexers = useIndexers
|
||||
|
||||
def clean_series_name(self, series_name):
|
||||
"""Cleans up series name by removing any . and _
|
||||
@ -70,104 +81,128 @@ class NameParser(object):
|
||||
def _compile_regexes(self, regexMode):
|
||||
if regexMode <= self.ALL_REGEX:
|
||||
logger.log(u"Using ALL regexs", logger.DEBUG)
|
||||
uncompiled_regex = regexes.sports_regexs + regexes.ep_regexes
|
||||
uncompiled_regex = [regexes.anime_regexes, regexes.sports_regexs, regexes.normal_regexes]
|
||||
|
||||
elif regexMode == self.NORMAL_REGEX:
|
||||
logger.log(u"Using NORMAL regexs", logger.DEBUG)
|
||||
uncompiled_regex = regexes.ep_regexes
|
||||
uncompiled_regex = [regexes.normal_regexes]
|
||||
|
||||
elif regexMode == self.SPORTS_REGEX:
|
||||
logger.log(u"Using SPORTS regexs", logger.DEBUG)
|
||||
uncompiled_regex = regexes.sports_regexs
|
||||
uncompiled_regex = [regexes.sports_regexs]
|
||||
|
||||
elif regexMode == self.ANIME_REGEX:
|
||||
logger.log(u"Using ANIME regexs", logger.DEBUG)
|
||||
uncompiled_regex = [regexes.anime_regexes, regexes.normal_regexes]
|
||||
|
||||
else:
|
||||
logger.log(u"This is a programing ERROR. Fallback Using NORMAL regexs", logger.ERROR)
|
||||
uncompiled_regex = regexes.ep_regexes
|
||||
uncompiled_regex = [regexes.normal_regexes]
|
||||
|
||||
for (cur_pattern_name, cur_pattern) in uncompiled_regex:
|
||||
try:
|
||||
cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE)
|
||||
except re.error, errormsg:
|
||||
logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern))
|
||||
else:
|
||||
self.compiled_regexes.append((cur_pattern_name, cur_regex))
|
||||
for regexItem in uncompiled_regex:
|
||||
for regex_type, regex in regexItem.items():
|
||||
try:
|
||||
self.compiled_regexes[regex_type]
|
||||
except:
|
||||
self.compiled_regexes[regex_type] = {}
|
||||
|
||||
for (cur_pattern_name, cur_pattern) in regex:
|
||||
try:
|
||||
cur_regex = re.compile(cur_pattern, re.VERBOSE | re.IGNORECASE)
|
||||
except re.error, errormsg:
|
||||
logger.log(u"WARNING: Invalid episode_pattern, %s. %s" % (errormsg, cur_pattern))
|
||||
else:
|
||||
self.compiled_regexes[regex_type].update({cur_pattern_name: cur_regex})
|
||||
|
||||
def _parse_string(self, name):
|
||||
for cur_regex_type, cur_regexes in self.compiled_regexes.items() if name else []:
|
||||
for cur_regex_name, cur_regex in cur_regexes.items():
|
||||
match = cur_regex.match(name)
|
||||
|
||||
if not name:
|
||||
return None
|
||||
|
||||
for (cur_regex_name, cur_regex) in self.compiled_regexes:
|
||||
|
||||
match = cur_regex.match(name)
|
||||
|
||||
if not match:
|
||||
continue
|
||||
|
||||
result = ParseResult(name)
|
||||
result.which_regex = [cur_regex_name]
|
||||
|
||||
named_groups = match.groupdict().keys()
|
||||
|
||||
if 'series_name' in named_groups:
|
||||
result.series_name = match.group('series_name')
|
||||
if result.series_name:
|
||||
result.series_name = self.clean_series_name(result.series_name)
|
||||
|
||||
if 'season_num' in named_groups:
|
||||
tmp_season = int(match.group('season_num'))
|
||||
if cur_regex_name == 'bare' and tmp_season in (19, 20):
|
||||
if not match:
|
||||
continue
|
||||
result.season_number = tmp_season
|
||||
|
||||
if 'ep_num' in named_groups:
|
||||
ep_num = self._convert_number(match.group('ep_num'))
|
||||
if 'extra_ep_num' in named_groups and match.group('extra_ep_num'):
|
||||
result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num')) + 1)
|
||||
else:
|
||||
result.episode_numbers = [ep_num]
|
||||
result = ParseResult(name)
|
||||
result.which_regex = [cur_regex_name]
|
||||
|
||||
if 'sports_event_id' in named_groups:
|
||||
sports_event_id = match.group('sports_event_id')
|
||||
if sports_event_id:
|
||||
result.sports_event_id = int(match.group('sports_event_id'))
|
||||
named_groups = match.groupdict().keys()
|
||||
|
||||
if 'sports_event_name' in named_groups:
|
||||
result.sports_event_name = match.group('sports_event_name')
|
||||
if result.sports_event_name:
|
||||
result.sports_event_name = self.clean_series_name(result.sports_event_name)
|
||||
if 'series_name' in named_groups:
|
||||
result.series_name = match.group('series_name')
|
||||
if result.series_name:
|
||||
result.series_name = self.clean_series_name(result.series_name)
|
||||
|
||||
if 'season_num' in named_groups:
|
||||
tmp_season = int(match.group('season_num'))
|
||||
if cur_regex_name == 'bare' and tmp_season in (19, 20):
|
||||
continue
|
||||
result.season_number = tmp_season
|
||||
|
||||
if 'ep_num' in named_groups:
|
||||
ep_num = self._convert_number(match.group('ep_num'))
|
||||
if 'extra_ep_num' in named_groups and match.group('extra_ep_num'):
|
||||
result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num')) + 1)
|
||||
else:
|
||||
result.episode_numbers = [ep_num]
|
||||
|
||||
if 'ep_ab_num' in named_groups:
|
||||
ep_ab_num = self._convert_number(match.group('ep_ab_num'))
|
||||
if 'extra_ab_ep_num' in named_groups and match.group('extra_ab_ep_num'):
|
||||
result.ab_episode_numbers = range(ep_ab_num,
|
||||
self._convert_number(match.group('extra_ab_ep_num')) + 1)
|
||||
else:
|
||||
result.ab_episode_numbers = [ep_ab_num]
|
||||
|
||||
if 'sports_event_id' in named_groups:
|
||||
sports_event_id = match.group('sports_event_id')
|
||||
if sports_event_id:
|
||||
result.sports_event_id = int(match.group('sports_event_id'))
|
||||
|
||||
if 'sports_event_name' in named_groups:
|
||||
result.sports_event_name = match.group('sports_event_name')
|
||||
if result.sports_event_name:
|
||||
result.sports_event_name = self.clean_series_name(result.sports_event_name)
|
||||
|
||||
if 'sports_event_date' in named_groups:
|
||||
sports_event_date = match.group('sports_event_date')
|
||||
if sports_event_date:
|
||||
try:
|
||||
result.sports_event_date = parser.parse(sports_event_date, fuzzy=True).date()
|
||||
except:
|
||||
continue
|
||||
|
||||
if 'air_year' in named_groups and 'air_month' in named_groups and 'air_day' in named_groups:
|
||||
year = int(match.group('air_year'))
|
||||
month = int(match.group('air_month'))
|
||||
day = int(match.group('air_day'))
|
||||
|
||||
if 'sports_event_date' in named_groups:
|
||||
sports_event_date = match.group('sports_event_date')
|
||||
if sports_event_date:
|
||||
try:
|
||||
result.sports_event_date = parser.parse(sports_event_date, fuzzy=True).date()
|
||||
dtStr = '%s-%s-%s' % (year, month, day)
|
||||
result.air_date = datetime.datetime.strptime(dtStr, "%Y-%m-%d").date()
|
||||
except:
|
||||
continue
|
||||
|
||||
if 'air_year' in named_groups and 'air_month' in named_groups and 'air_day' in named_groups:
|
||||
year = int(match.group('air_year'))
|
||||
month = int(match.group('air_month'))
|
||||
day = int(match.group('air_day'))
|
||||
if 'extra_info' in named_groups:
|
||||
tmp_extra_info = match.group('extra_info')
|
||||
|
||||
try:
|
||||
dtStr = '%s-%s-%s' % (year, month, day)
|
||||
result.air_date = datetime.datetime.strptime(dtStr, "%Y-%m-%d").date()
|
||||
except:
|
||||
continue
|
||||
# Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season
|
||||
if tmp_extra_info and cur_regex_name == 'season_only' and re.search(
|
||||
r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I):
|
||||
continue
|
||||
result.extra_info = tmp_extra_info
|
||||
|
||||
if 'extra_info' in named_groups:
|
||||
tmp_extra_info = match.group('extra_info')
|
||||
if 'release_group' in named_groups:
|
||||
result.release_group = match.group('release_group')
|
||||
|
||||
# Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season
|
||||
if tmp_extra_info and cur_regex_name == 'season_only' and re.search(r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I):
|
||||
continue
|
||||
result.extra_info = tmp_extra_info
|
||||
|
||||
if 'release_group' in named_groups:
|
||||
result.release_group = match.group('release_group')
|
||||
|
||||
return result
|
||||
show = helpers.get_show_by_name(result.series_name, useIndexer=self.useIndexers)
|
||||
if show and show.is_anime and cur_regex_type in ['anime', 'normal']:
|
||||
result.show = show
|
||||
return result
|
||||
elif show and show.is_sports and cur_regex_type == 'sports':
|
||||
result.show = show
|
||||
return result
|
||||
elif cur_regex_type == 'normal':
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
@ -248,6 +283,7 @@ class NameParser(object):
|
||||
|
||||
# build the ParseResult object
|
||||
final_result.air_date = self._combine_results(file_name_result, dir_name_result, 'air_date')
|
||||
final_result.ab_episode_numbers = self._combine_results(file_name_result, dir_name_result, 'ab_episode_numbers')
|
||||
|
||||
# sports event title
|
||||
final_result.sports_event_id = self._combine_results(file_name_result, dir_name_result, 'sports_event_id')
|
||||
@ -275,6 +311,20 @@ class NameParser(object):
|
||||
if dir_name_result:
|
||||
final_result.which_regex += dir_name_result.which_regex
|
||||
|
||||
final_result.show = self._combine_results(file_name_result, dir_name_result, 'show')
|
||||
if final_result.show and final_result.show.is_anime and final_result.is_anime: # only need to to do another conversion if the scene2tvdb didn work
|
||||
logger.log("Getting season and episodes from absolute numbers", logger.DEBUG)
|
||||
try:
|
||||
_actual_season, _actual_episodes = helpers.get_all_episodes_from_absolute_number(final_result.show,
|
||||
None,
|
||||
final_result.ab_episode_numbers)
|
||||
except EpisodeNotFoundByAbsoluteNumberException:
|
||||
logger.log(str(final_result.show.indexerid) + ": Indexer object absolute number " + str(
|
||||
final_result.ab_episode_numbers) + " is incomplete, cant determin season and episode numbers")
|
||||
else:
|
||||
final_result.season = _actual_season
|
||||
final_result.episodes = _actual_episodes
|
||||
|
||||
# if there's no useful info in it then raise an exception
|
||||
if final_result.season_number == None and not final_result.episode_numbers and final_result.air_date == None and not final_result.series_name:
|
||||
raise InvalidNameException("Unable to parse " + name.encode(sickbeard.SYS_ENCODING, 'xmlcharrefreplace'))
|
||||
@ -295,6 +345,8 @@ class ParseResult(object):
|
||||
extra_info=None,
|
||||
release_group=None,
|
||||
air_date=None,
|
||||
ab_episode_numbers=None,
|
||||
show=None
|
||||
):
|
||||
|
||||
self.original_name = original_name
|
||||
@ -306,6 +358,11 @@ class ParseResult(object):
|
||||
else:
|
||||
self.episode_numbers = episode_numbers
|
||||
|
||||
if not ab_episode_numbers:
|
||||
self.ab_episode_numbers = []
|
||||
else:
|
||||
self.ab_episode_numbers = ab_episode_numbers
|
||||
|
||||
self.extra_info = extra_info
|
||||
self.release_group = release_group
|
||||
|
||||
@ -316,6 +373,7 @@ class ParseResult(object):
|
||||
self.sports_event_date = sports_event_date
|
||||
|
||||
self.which_regex = None
|
||||
self.show = show
|
||||
|
||||
def __eq__(self, other):
|
||||
if not other:
|
||||
@ -339,6 +397,10 @@ class ParseResult(object):
|
||||
return False
|
||||
if self.sports_event_date != other.sports_event_date:
|
||||
return False
|
||||
if self.ab_episode_numbers != other.ab_episode_numbers:
|
||||
return False
|
||||
if self.show != other.show:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@ -359,6 +421,8 @@ class ParseResult(object):
|
||||
to_return += str(self.sports_event_name)
|
||||
to_return += str(self.sports_event_id)
|
||||
to_return += str(self.sports_event_date)
|
||||
if self.ab_episode_numbers:
|
||||
to_return += ' absolute_numbers: ' + str(self.ab_episode_numbers)
|
||||
|
||||
if self.extra_info:
|
||||
to_return += ' - ' + self.extra_info
|
||||
@ -367,23 +431,31 @@ class ParseResult(object):
|
||||
|
||||
to_return += ' [ABD: ' + str(self.air_by_date) + ']'
|
||||
to_return += ' [SPORTS: ' + str(self.sports) + ']'
|
||||
to_return += ' [ANIME: ' + str(self.is_anime) + ']'
|
||||
to_return += ' [whichReg: ' + str(self.which_regex) + ']'
|
||||
|
||||
return to_return.encode('utf-8')
|
||||
|
||||
def convert(self, show):
|
||||
if not show: return self # need show object
|
||||
if not show: return self # need show object
|
||||
if not self.season_number: return self # can't work without a season
|
||||
if not len(self.episode_numbers): return self # need at least one episode
|
||||
if self.air_by_date or self.sports: return self # scene numbering does not apply to air-by-date
|
||||
|
||||
new_episode_numbers = []
|
||||
new_season_numbers = []
|
||||
for epNo in self.episode_numbers:
|
||||
(s, e) = scene_numbering.get_indexer_numbering(show.indexerid, show.indexer, self.season_number,
|
||||
epNo)
|
||||
new_absolute_numbers = []
|
||||
|
||||
for i, epNo in enumerate(self.episode_numbers):
|
||||
abNo = None
|
||||
if len(self.ab_episode_numbers):
|
||||
abNo = self.ab_episode_numbers[i]
|
||||
|
||||
(s, e, a) = scene_numbering.get_indexer_numbering(show.indexerid, show.indexer, self.season_number,
|
||||
epNo, abNo)
|
||||
new_episode_numbers.append(e)
|
||||
new_season_numbers.append(s)
|
||||
new_absolute_numbers.append(a)
|
||||
|
||||
# need to do a quick sanity check here. It's possible that we now have episodes
|
||||
# from more than one season (by tvdb numbering), and this is just too much
|
||||
@ -400,6 +472,11 @@ class ParseResult(object):
|
||||
new_episode_numbers = list(set(new_episode_numbers))
|
||||
new_episode_numbers.sort()
|
||||
|
||||
# dedupe absolute numbers
|
||||
new_absolute_numbers = list(set(new_absolute_numbers))
|
||||
new_absolute_numbers.sort()
|
||||
|
||||
self.ab_episode_numbers = new_absolute_numbers
|
||||
self.episode_numbers = new_episode_numbers
|
||||
self.season_number = new_season_numbers[0]
|
||||
|
||||
@ -412,6 +489,13 @@ class ParseResult(object):
|
||||
|
||||
air_by_date = property(_is_air_by_date)
|
||||
|
||||
def _is_anime(self):
|
||||
if self.ab_episode_numbers:
|
||||
return True
|
||||
return False
|
||||
|
||||
is_anime = property(_is_anime)
|
||||
|
||||
def _is_sports(self):
|
||||
if self.sports_event_date:
|
||||
return True
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
# all regexes are case insensitive
|
||||
|
||||
ep_regexes = [
|
||||
normal_regexes = {'normal':[
|
||||
('standard_repeat',
|
||||
# Show.Name.S01E02.S01E03.Source.Quality.Etc-Group
|
||||
# Show Name - S01E02 - S01E03 - S01E04 - Ep Name
|
||||
@ -184,9 +184,9 @@ ep_regexes = [
|
||||
-(?P<release_group>[^- ]+))?)?$ # Group
|
||||
'''
|
||||
),
|
||||
]
|
||||
]}
|
||||
|
||||
sports_regexs = [
|
||||
sports_regexs = {'sports':[
|
||||
|
||||
('sports_standard',
|
||||
# Sports.Name.2010.11.23.Source.Quality.Etc-Group
|
||||
@ -200,4 +200,175 @@ sports_regexs = [
|
||||
-(?P<release_group>[^- ]+))?)?$
|
||||
'''
|
||||
),
|
||||
]
|
||||
]}
|
||||
|
||||
anime_regexes = {'anime':[
|
||||
|
||||
('anime_ultimate',
|
||||
"""
|
||||
^(?:\[(?P<release_group>.+?)\][ ._-]*)
|
||||
(?P<series_name>.+?)[ ._-]+
|
||||
(?P<ep_ab_num>\d{1,3})
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))?[ ._-]+?
|
||||
(?:v(?P<version>[0-9]))?
|
||||
(?:[\w\.]*)
|
||||
(?:(?:(?:[\[\(])(?P<extra_info>\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)(?:[\]\)]))|(?:\d{3,4}[xp]))
|
||||
(?:[ ._]?\[(?P<crc>\w+)\])?
|
||||
.*?
|
||||
"""
|
||||
),
|
||||
('anime_standard',
|
||||
# [Group Name] Show Name.13-14
|
||||
# [Group Name] Show Name - 13-14
|
||||
# Show Name 13-14
|
||||
# [Group Name] Show Name.13
|
||||
# [Group Name] Show Name - 13
|
||||
# Show Name 13
|
||||
'''
|
||||
^(\[(?P<release_group>.+?)\][ ._-]*)? # Release Group and separator
|
||||
(?P<series_name>.+?)[ ._-]+ # Show_Name and separator
|
||||
(?P<ep_ab_num>\d{1,3}) # E01
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # E02
|
||||
(v(?P<version>[0-9]))? # version
|
||||
[ ._-]+\[(?P<extra_info>\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)\] # Source_Quality_Etc-
|
||||
(\[(?P<crc>\w{8})\])? # CRC
|
||||
.*? # Separator and EOL
|
||||
'''),
|
||||
|
||||
('anime_standard_round',
|
||||
# TODO examples
|
||||
# [Stratos-Subs]_Infinite_Stratos_-_12_(1280x720_H.264_AAC)_[379759DB]
|
||||
# [ShinBunBu-Subs] Bleach - 02-03 (CX 1280x720 x264 AAC)
|
||||
'''
|
||||
^(\[(?P<release_group>.+?)\][ ._-]*)? # Release Group and separator
|
||||
(?P<series_name>.+?)[ ._-]+ # Show_Name and separator
|
||||
(?P<ep_ab_num>\d{1,3}) # E01
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # E02
|
||||
(v(?P<version>[0-9]))? # version
|
||||
[ ._-]+\((?P<extra_info>(CX[ ._-]?)?\d{3,4}[xp]?\d{0,4}[\.\w\s-]*)\) # Source_Quality_Etc-
|
||||
(\[(?P<crc>\w{8})\])? # CRC
|
||||
.*? # Separator and EOL
|
||||
'''),
|
||||
|
||||
('anime_slash',
|
||||
# [SGKK] Bleach 312v1 [720p/MKV]
|
||||
'''
|
||||
^(\[(?P<release_group>.+?)\][ ._-]*)? # Release Group and separator
|
||||
(?P<series_name>.+?)[ ._-]+ # Show_Name and separator
|
||||
(?P<ep_ab_num>\d{1,3}) # E01
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # E02
|
||||
(v(?P<version>[0-9]))? # version
|
||||
[ ._-]+\[(?P<extra_info>\d{3,4}p) # Source_Quality_Etc-
|
||||
(\[(?P<crc>\w{8})\])? # CRC
|
||||
.*? # Separator and EOL
|
||||
'''),
|
||||
|
||||
('anime_standard_codec',
|
||||
# [Ayako]_Infinite_Stratos_-_IS_-_07_[H264][720p][EB7838FC]
|
||||
# [Ayako] Infinite Stratos - IS - 07v2 [H264][720p][44419534]
|
||||
# [Ayako-Shikkaku] Oniichan no Koto Nanka Zenzen Suki Janain Dakara ne - 10 [LQ][h264][720p] [8853B21C]
|
||||
'''
|
||||
^(\[(?P<release_group>.+?)\][ ._-]*)? # Release Group and separator
|
||||
(?P<series_name>.+?)[ ._]* # Show_Name and separator
|
||||
([ ._-]+-[ ._-]+[A-Z]+[ ._-]+)?[ ._-]+ # funny stuff, this is sooo nuts ! this will kick me in the butt one day
|
||||
(?P<ep_ab_num>\d{1,3}) # E01
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # E02
|
||||
(v(?P<version>[0-9]))? # version
|
||||
([ ._-](\[\w{1,2}\])?\[[a-z][.]?\w{2,4}\])? #codec
|
||||
[ ._-]*\[(?P<extra_info>(\d{3,4}[xp]?\d{0,4})?[\.\w\s-]*)\] # Source_Quality_Etc-
|
||||
(\[(?P<crc>\w{8})\])?
|
||||
.*? # Separator and EOL
|
||||
'''),
|
||||
|
||||
('anime_and_normal',
|
||||
# Bleach - s16e03-04 - 313-314
|
||||
# Bleach.s16e03-04.313-314
|
||||
# Bleach s16e03e04 313-314
|
||||
'''
|
||||
^(?P<series_name>.+?)[ ._-]+ # start of string and series name and non optinal separator
|
||||
[sS](?P<season_num>\d+)[. _-]* # S01 and optional separator
|
||||
[eE](?P<ep_num>\d+) # epipisode E02
|
||||
(([. _-]*e|-) # linking e/- char
|
||||
(?P<extra_ep_num>\d+))* # additional E03/etc
|
||||
([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be there(->{2,}) "s16e03-04-313-314" would make sens any way
|
||||
(?P<ep_ab_num>\d{1,3}) # absolute number
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # "-" as separator and anditional absolute number, all optinal
|
||||
(v(?P<version>[0-9]))? # the version e.g. "v2"
|
||||
.*?
|
||||
'''
|
||||
|
||||
),
|
||||
('anime_and_normal_x',
|
||||
# Bleach - s16e03-04 - 313-314
|
||||
# Bleach.s16e03-04.313-314
|
||||
# Bleach s16e03e04 313-314
|
||||
'''
|
||||
^(?P<series_name>.+?)[ ._-]+ # start of string and series name and non optinal separator
|
||||
(?P<season_num>\d+)[. _-]* # S01 and optional separator
|
||||
[xX](?P<ep_num>\d+) # epipisode E02
|
||||
(([. _-]*e|-) # linking e/- char
|
||||
(?P<extra_ep_num>\d+))* # additional E03/etc
|
||||
([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be there(->{2,}) "s16e03-04-313-314" would make sens any way
|
||||
(?P<ep_ab_num>\d{1,3}) # absolute number
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # "-" as separator and anditional absolute number, all optinal
|
||||
(v(?P<version>[0-9]))? # the version e.g. "v2"
|
||||
.*?
|
||||
'''
|
||||
|
||||
),
|
||||
|
||||
('anime_and_normal_reverse',
|
||||
# Bleach - 313-314 - s16e03-04
|
||||
'''
|
||||
^(?P<series_name>.+?)[ ._-]+ # start of string and series name and non optinal separator
|
||||
(?P<ep_ab_num>\d{1,3}) # absolute number
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # "-" as separator and anditional absolute number, all optinal
|
||||
(v(?P<version>[0-9]))? # the version e.g. "v2"
|
||||
([ ._-]{2,}|[ ._]+) # if "-" is used to separate at least something else has to be there(->{2,}) "s16e03-04-313-314" would make sens any way
|
||||
[sS](?P<season_num>\d+)[. _-]* # S01 and optional separator
|
||||
[eE](?P<ep_num>\d+) # epipisode E02
|
||||
(([. _-]*e|-) # linking e/- char
|
||||
(?P<extra_ep_num>\d+))* # additional E03/etc
|
||||
.*?
|
||||
'''
|
||||
),
|
||||
|
||||
('anime_and_normal_front',
|
||||
# 165.Naruto Shippuuden.s08e014
|
||||
'''
|
||||
^(?P<ep_ab_num>\d{1,3}) # start of string and absolute number
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))? # "-" as separator and anditional absolute number, all optinal
|
||||
(v(?P<version>[0-9]))?[ ._-]+ # the version e.g. "v2"
|
||||
(?P<series_name>.+?)[ ._-]+
|
||||
[sS](?P<season_num>\d+)[. _-]* # S01 and optional separator
|
||||
[eE](?P<ep_num>\d+)
|
||||
(([. _-]*e|-) # linking e/- char
|
||||
(?P<extra_ep_num>\d+))* # additional E03/etc
|
||||
.*?
|
||||
'''
|
||||
),
|
||||
('anime_ep_name',
|
||||
"""
|
||||
^(?:\[(?P<release_group>.+?)\][ ._-]*)
|
||||
(?P<series_name>.+?)[ ._-]+
|
||||
(?P<ep_ab_num>\d{1,3})
|
||||
(-(?P<extra_ab_ep_num>\d{1,3}))?[ ._-]*?
|
||||
(?:v(?P<version>[0-9])[ ._-]+?)?
|
||||
(?:.+?[ ._-]+?)?
|
||||
\[(?P<extra_info>\w+)\][ ._-]?
|
||||
(?:\[(?P<crc>\w{8})\])?
|
||||
.*?
|
||||
"""
|
||||
),
|
||||
('anime_bare',
|
||||
# One Piece - 102
|
||||
# [ACX]_Wolf's_Spirit_001.mkv
|
||||
'''
|
||||
^(\[(?P<release_group>.+?)\][ ._-]*)?
|
||||
(?P<series_name>.+?)[ ._-]+ # Show_Name and separator
|
||||
(?P<ep_ab_num>\d{3}) # E01
|
||||
(-(?P<extra_ab_ep_num>\d{3}))? # E02
|
||||
(v(?P<version>[0-9]))? # v2
|
||||
.*? # Separator and EOL
|
||||
''')
|
||||
]}
|
@ -53,13 +53,15 @@ class TVShow():
|
||||
self.genre = "Comedy"
|
||||
self.air_by_date = 0
|
||||
self.sports = 0
|
||||
self.anime = 0
|
||||
|
||||
class TVEpisode(tv.TVEpisode):
|
||||
def __init__(self, season, episode, name):
|
||||
def __init__(self, season, episode, absolute_number, name):
|
||||
self.relatedEps = []
|
||||
self._name = name
|
||||
self._season = season
|
||||
self._episode = episode
|
||||
self._absolute_number = absolute_number
|
||||
self._scene_season = season
|
||||
self._scene_episode = episode
|
||||
self._airdate = datetime.date(2010, 3, 9)
|
||||
@ -173,9 +175,9 @@ def validate_name(pattern, multi=None, file_only=False, abd=False, sports=False)
|
||||
return True
|
||||
|
||||
|
||||
def _generate_sample_ep(multi=None, abd=False, sports=False):
|
||||
def _generate_sample_ep(multi=None, abd=False, sports=False, anime=False):
|
||||
# make a fake episode object
|
||||
ep = TVEpisode(2, 3, "Ep Name")
|
||||
ep = TVEpisode(2, 3, 3, "Ep Name")
|
||||
|
||||
ep._status = Quality.compositeStatus(DOWNLOADED, Quality.HDTV)
|
||||
ep._airdate = datetime.date(2011, 3, 9)
|
||||
@ -186,6 +188,9 @@ def _generate_sample_ep(multi=None, abd=False, sports=False):
|
||||
elif sports:
|
||||
ep._release_name = 'Show.Name.100.Fighter.vs.Fighter.HDTV.XviD-RLSGROUP'
|
||||
ep.show.sports = 1
|
||||
elif anime:
|
||||
ep._release_name = 'Show.Name.S02E03.HDTV.XviD-RLSGROUP'
|
||||
ep.show.anime = 1
|
||||
else:
|
||||
ep._release_name = 'Show.Name.S02E03.HDTV.XviD-RLSGROUP'
|
||||
|
||||
@ -193,11 +198,11 @@ def _generate_sample_ep(multi=None, abd=False, sports=False):
|
||||
ep._name = "Ep Name (1)"
|
||||
ep._release_name = 'Show.Name.S02E03E04E05.HDTV.XviD-RLSGROUP'
|
||||
|
||||
secondEp = TVEpisode(2, 4, "Ep Name (2)")
|
||||
secondEp = TVEpisode(2, 4, 4, "Ep Name (2)")
|
||||
secondEp._status = Quality.compositeStatus(DOWNLOADED, Quality.HDTV)
|
||||
secondEp._release_name = ep._release_name
|
||||
|
||||
thirdEp = TVEpisode(2, 5, "Ep Name (3)")
|
||||
thirdEp = TVEpisode(2, 5, 5, "Ep Name (3)")
|
||||
thirdEp._status = Quality.compositeStatus(DOWNLOADED, Quality.HDTV)
|
||||
thirdEp._release_name = ep._release_name
|
||||
|
||||
@ -207,7 +212,7 @@ def _generate_sample_ep(multi=None, abd=False, sports=False):
|
||||
return ep
|
||||
|
||||
|
||||
def test_name(pattern, multi=None, abd=False, sports=False):
|
||||
ep = _generate_sample_ep(multi, abd, sports)
|
||||
def test_name(pattern, multi=None, abd=False, sports=False, anime=False):
|
||||
ep = _generate_sample_ep(multi, abd, sports, anime)
|
||||
|
||||
return {'name': ep.formatted_filename(pattern, multi), 'dir': ep.formatted_dir(pattern, multi)}
|
@ -70,7 +70,7 @@ class BoxcarNotifier:
|
||||
handle = urllib2.urlopen(req, data)
|
||||
handle.close()
|
||||
|
||||
except urllib2.URLError, e:
|
||||
except urllib2.HTTPError, e:
|
||||
# if we get an error back that doesn't have an error code then who knows what's really happening
|
||||
if not hasattr(e, 'code'):
|
||||
logger.log("Boxcar notification failed." + ex(e), logger.ERROR)
|
||||
|
@ -63,7 +63,7 @@ class Boxcar2Notifier:
|
||||
handle = urllib2.urlopen(req, data)
|
||||
handle.close()
|
||||
|
||||
except urllib2.URLError, e:
|
||||
except urllib2.HTTPError, e:
|
||||
# if we get an error back that doesn't have an error code then who knows what's really happening
|
||||
if not hasattr(e, 'code'):
|
||||
logger.log("Boxcar2 notification failed." + ex(e), logger.ERROR)
|
||||
|
@ -68,7 +68,7 @@ class PushoverNotifier:
|
||||
handle = urllib2.urlopen(req, data)
|
||||
handle.close()
|
||||
|
||||
except urllib2.URLError, e:
|
||||
except urllib2.HTTPError, e:
|
||||
# if we get an error back that doesn't have an error code then who knows what's really happening
|
||||
if not hasattr(e, 'code'):
|
||||
logger.log("Pushover notification failed." + ex(e), logger.ERROR)
|
||||
@ -127,10 +127,6 @@ class PushoverNotifier:
|
||||
logger.log("Notification for Pushover not enabled, skipping this notification", logger.DEBUG)
|
||||
return False
|
||||
|
||||
# if no userKey was given then use the one from the config
|
||||
if not userKey:
|
||||
userKey = sickbeard.PUSHOVER_USERKEY
|
||||
|
||||
logger.log("Sending notification for " + message, logger.DEBUG)
|
||||
|
||||
# self._sendPushover(message, title, userKey)
|
||||
|
@ -20,7 +20,7 @@ import os
|
||||
import sickbeard
|
||||
|
||||
from urllib import urlencode
|
||||
from urllib2 import Request, urlopen, URLError
|
||||
from urllib2 import Request, urlopen, HTTPError
|
||||
|
||||
from sickbeard import logger
|
||||
from sickbeard import encodingKludge as ek
|
||||
@ -87,7 +87,7 @@ class pyTivoNotifier:
|
||||
|
||||
try:
|
||||
response = urlopen(request) #@UnusedVariable
|
||||
except URLError, e:
|
||||
except HTTPError , e:
|
||||
if hasattr(e, 'reason'):
|
||||
logger.log(u"pyTivo notification: Error, failed to reach a server")
|
||||
logger.log(u"'Error reason: " + e.reason)
|
||||
|
@ -21,15 +21,12 @@ from __future__ import with_statement
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import stat
|
||||
import copy
|
||||
|
||||
import sickbeard
|
||||
|
||||
from sickbeard import db
|
||||
from sickbeard import classes
|
||||
from sickbeard import common
|
||||
from sickbeard import exceptions
|
||||
from sickbeard import helpers
|
||||
@ -37,7 +34,6 @@ from sickbeard import history
|
||||
from sickbeard import logger
|
||||
from sickbeard import notifiers
|
||||
from sickbeard import show_name_helpers
|
||||
from sickbeard import scene_exceptions
|
||||
from sickbeard import failed_history
|
||||
from sickbeard import name_cache
|
||||
|
||||
@ -46,6 +42,7 @@ from sickbeard.exceptions import ex
|
||||
|
||||
from sickbeard.name_parser.parser import NameParser, InvalidNameException
|
||||
|
||||
from lib import adba
|
||||
|
||||
class PostProcessor(object):
|
||||
"""
|
||||
@ -483,7 +480,7 @@ class PostProcessor(object):
|
||||
return to_return
|
||||
|
||||
# parse the name to break it into show name, season, and episode
|
||||
np = NameParser(file)
|
||||
np = NameParser(file, useIndexers=True)
|
||||
parse_result = np.parse(name)
|
||||
|
||||
self._log(u"Parsed " + name + " into " + str(parse_result).decode('utf-8', 'xmlcharrefreplace'), logger.DEBUG)
|
||||
@ -508,6 +505,72 @@ class PostProcessor(object):
|
||||
self._finalize(parse_result)
|
||||
return to_return
|
||||
|
||||
def _analyze_anidb(self, filePath):
|
||||
# TODO: rewrite this
|
||||
return (None, None, None)
|
||||
|
||||
if not helpers.set_up_anidb_connection():
|
||||
return (None, None, None)
|
||||
|
||||
ep = self._build_anidb_episode(sickbeard.ADBA_CONNECTION, filePath)
|
||||
try:
|
||||
self._log(u"Trying to lookup " + str(filePath) + " on anidb", logger.MESSAGE)
|
||||
ep.load_data()
|
||||
except Exception, e:
|
||||
self._log(u"exception msg: " + str(e))
|
||||
raise InvalidNameException
|
||||
else:
|
||||
self.anidbEpisode = ep
|
||||
|
||||
#TODO: clean code. it looks like it's from hell
|
||||
for name in ep.allNames:
|
||||
|
||||
indexer_id = name_cache.retrieveNameFromCache(name)
|
||||
if not indexer_id:
|
||||
show = helpers.get_show_by_name(name)
|
||||
if show:
|
||||
indexer_id = show.indexerid
|
||||
else:
|
||||
indexer_id = 0
|
||||
|
||||
if indexer_id:
|
||||
name_cache.addNameToCache(name, indexer_id)
|
||||
if indexer_id:
|
||||
try:
|
||||
show = helpers.findCertainShow(sickbeard.showList, indexer_id)
|
||||
(season, episodes) = helpers.get_all_episodes_from_absolute_number(show, None, [ep.epno])
|
||||
except exceptions.EpisodeNotFoundByAbsoluteNumberException:
|
||||
self._log(str(indexer_id) + ": Indexer object absolute number " + str(
|
||||
ep.epno) + " is incomplete, skipping this episode")
|
||||
else:
|
||||
if len(episodes):
|
||||
self._log(u"Lookup successful from anidb. ", logger.DEBUG)
|
||||
return (indexer_id, season, episodes)
|
||||
|
||||
if ep.anidb_file_name:
|
||||
self._log(u"Lookup successful, using anidb filename " + str(ep.anidb_file_name), logger.DEBUG)
|
||||
return self._analyze_name(ep.anidb_file_name)
|
||||
raise InvalidNameException
|
||||
|
||||
|
||||
def _build_anidb_episode(self, connection, filePath):
|
||||
ep = adba.Episode(connection, filePath=filePath,
|
||||
paramsF=["quality", "anidb_file_name", "crc32"],
|
||||
paramsA=["epno", "english_name", "short_name_list", "other_name", "synonym_list"])
|
||||
|
||||
return ep
|
||||
|
||||
def _add_to_anidb_mylist(self, filePath):
|
||||
if helpers.set_up_anidb_connection():
|
||||
if not self.anidbEpisode: # seams like we could parse the name before, now lets build the anidb object
|
||||
self.anidbEpisode = self._build_anidb_episode(sickbeard.ADBA_CONNECTION, filePath)
|
||||
|
||||
self._log(u"Adding the file to the anidb mylist", logger.DEBUG)
|
||||
try:
|
||||
self.anidbEpisode.add_to_mylist(status=1) # status = 1 sets the status of the file to "internal HDD"
|
||||
except Exception, e:
|
||||
self._log(u"exception msg: " + str(e))
|
||||
|
||||
def _find_info(self):
|
||||
"""
|
||||
For a given file try to find the showid, season, and episode.
|
||||
@ -676,7 +739,7 @@ class PostProcessor(object):
|
||||
if not cur_name:
|
||||
continue
|
||||
|
||||
ep_quality = common.Quality.nameQuality(cur_name)
|
||||
ep_quality = common.Quality.nameQuality(cur_name, ep_obj.show.is_anime)
|
||||
self._log(
|
||||
u"Looking up quality for name " + cur_name + u", got " + common.Quality.qualityStrings[ep_quality],
|
||||
logger.DEBUG)
|
||||
@ -941,6 +1004,10 @@ class PostProcessor(object):
|
||||
new_base_name = None
|
||||
new_file_name = self.file_name
|
||||
|
||||
# add to anidb
|
||||
if ep_obj.show.is_anime and sickbeard.ANIDB_USE_MYLIST:
|
||||
self._add_to_anidb_mylist(self.file_path)
|
||||
|
||||
try:
|
||||
# move the episode and associated files to the show dir
|
||||
if self.process_method == "copy":
|
||||
|
@ -171,10 +171,15 @@ class ProperFinder():
|
||||
curProper.season = -1
|
||||
curProper.episode = parse_result.air_date or parse_result.sports_event_date
|
||||
else:
|
||||
curProper.season = parse_result.season_number if parse_result.season_number != None else 1
|
||||
curProper.episode = parse_result.episode_numbers[0]
|
||||
if parse_result.is_anime:
|
||||
logger.log(u"I am sorry '"+curProper.name+"' seams to be an anime proper seach is not yet suported", logger.DEBUG)
|
||||
continue
|
||||
curProper.episode = parse_result.ab_episode_numbers[0]
|
||||
else:
|
||||
curProper.season = parse_result.season_number if parse_result.season_number != None else 1
|
||||
curProper.episode = parse_result.episode_numbers[0]
|
||||
|
||||
curProper.quality = Quality.nameQuality(curProper.name)
|
||||
curProper.quality = Quality.nameQuality(curProper.name, parse_result.is_anime)
|
||||
|
||||
# for each show in our list
|
||||
for curShow in sickbeard.showList:
|
||||
|
@ -31,7 +31,9 @@ __all__ = ['ezrss',
|
||||
'iptorrents',
|
||||
'omgwtfnzbs',
|
||||
'nextgen',
|
||||
'speedcd'
|
||||
'speedcd',
|
||||
'nyaatorrents',
|
||||
'fanzub'
|
||||
]
|
||||
|
||||
import sickbeard
|
||||
@ -160,7 +162,7 @@ def makeTorrentRssProvider(configString):
|
||||
|
||||
|
||||
def getDefaultNewznabProviders():
|
||||
return 'SickRage Index|http://lolo.sickbeard.com/|0|5030,5040,5060|0|eponly|0!!!NZBs.org|https://nzbs.org/||5030,5040,5060,5070,5090|0|eponly|0!!!Usenet-Crawler|https://www.usenet-crawler.com/||5030,5040,5060|0|eponly|0'
|
||||
return 'Sick Beard Index|http://lolo.sickbeard.com/|0|5030,5040|0|eponly|0!!!NZBs.org|https://nzbs.org/||5030,5040|0|eponly|0!!!Usenet-Crawler|https://www.usenet-crawler.com/||5030,5040|0|eponly|0'
|
||||
|
||||
|
||||
def getProviderModule(name):
|
||||
|
@ -46,7 +46,7 @@ class DTTProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'dailytvtorrents.gif'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
url = item.enclosures[0].href
|
||||
quality = Quality.sceneQuality(url)
|
||||
return quality
|
||||
|
@ -53,7 +53,7 @@ class EZRSSProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'ezrss.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
filename = item.filename
|
||||
quality = Quality.nameQuality(filename)
|
||||
|
151
sickbeard/providers/fanzub.py
Normal file
151
sickbeard/providers/fanzub.py
Normal file
@ -0,0 +1,151 @@
|
||||
# Author: Nic Wolfe <nic@wolfeden.ca>
|
||||
# URL: http://code.google.com/p/sickbeard/
|
||||
#
|
||||
# This file is part of Sick Beard.
|
||||
#
|
||||
# Sick Beard is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Sick Beard is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import urllib
|
||||
|
||||
import sickbeard
|
||||
import generic
|
||||
|
||||
from sickbeard import classes, show_name_helpers, helpers
|
||||
|
||||
from sickbeard import exceptions, logger
|
||||
from sickbeard.common import *
|
||||
from sickbeard import tvcache
|
||||
from lib.dateutil.parser import parse as parseDate
|
||||
|
||||
class Fanzub(generic.NZBProvider):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
generic.NZBProvider.__init__(self, "Fanzub")
|
||||
|
||||
self.supportsBacklog = False
|
||||
self.supportsAbsoluteNumbering = True
|
||||
|
||||
self.enabled = False
|
||||
|
||||
self.cache = FanzubCache(self)
|
||||
|
||||
self.url = 'http://fanzub.com/'
|
||||
|
||||
def isEnabled(self):
|
||||
return self.enabled
|
||||
|
||||
def imageName(self):
|
||||
return 'fanzub.gif'
|
||||
|
||||
def _checkAuth(self):
|
||||
return True
|
||||
|
||||
def _get_season_search_strings(self, ep_obj):
|
||||
return [x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)]
|
||||
|
||||
def _get_episode_search_strings(self, ep_obj, add_string=''):
|
||||
return [x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)]
|
||||
|
||||
def _doSearch(self, search_string, epcount=0, age=0):
|
||||
if self.show and not self.show.is_anime:
|
||||
logger.log(u"" + str(self.show.name) + " is not an anime skiping ...")
|
||||
return []
|
||||
|
||||
params = {
|
||||
"cat": "anime",
|
||||
"q": search_string.encode('utf-8'),
|
||||
"max": "100"
|
||||
}
|
||||
|
||||
search_url = self.url + "rss?" + urllib.urlencode(params)
|
||||
|
||||
logger.log(u"Search url: " + search_url, logger.DEBUG)
|
||||
|
||||
data = self.cache.getRSSFeed(search_url)
|
||||
if not data:
|
||||
return []
|
||||
|
||||
if 'entries' in data:
|
||||
|
||||
items = data.entries
|
||||
results = []
|
||||
|
||||
for curItem in items:
|
||||
(title, url) = self._get_title_and_url(curItem)
|
||||
|
||||
if title and url:
|
||||
results.append(curItem)
|
||||
else:
|
||||
logger.log(
|
||||
u"The data returned from the " + self.name + " is incomplete, this result is unusable",
|
||||
logger.DEBUG)
|
||||
|
||||
return results
|
||||
|
||||
return []
|
||||
|
||||
def findPropers(self, date=None):
|
||||
|
||||
results = []
|
||||
|
||||
for i in [2, 3, 4]: # we will look for a version 2, 3 and 4
|
||||
"""
|
||||
because of this the proper search failed !!
|
||||
well more precisly because _doSearch does not accept a dict rather then a string
|
||||
params = {
|
||||
"q":"v"+str(i).encode('utf-8')
|
||||
}
|
||||
"""
|
||||
for curResult in self._doSearch("v" + str(i)):
|
||||
|
||||
match = re.search('(\w{3}, \d{1,2} \w{3} \d{4} \d\d:\d\d:\d\d) [\+\-]\d{4}', curResult.findtext('pubDate'))
|
||||
if not match:
|
||||
continue
|
||||
|
||||
dateString = match.group(1)
|
||||
resultDate = parseDate(dateString).replace(tzinfo=None)
|
||||
|
||||
if date == None or resultDate > date:
|
||||
results.append(classes.Proper(curResult.findtext('title'), curResult.findtext('link'), resultDate))
|
||||
|
||||
return results
|
||||
|
||||
class FanzubCache(tvcache.TVCache):
|
||||
|
||||
def __init__(self, provider):
|
||||
|
||||
tvcache.TVCache.__init__(self, provider)
|
||||
|
||||
# only poll Fanzub every 20 minutes max
|
||||
# we get 100 post each call !
|
||||
self.minTime = 20
|
||||
|
||||
|
||||
def _getRSSData(self):
|
||||
|
||||
params = {"cat": "anime".encode('utf-8'),
|
||||
"max": "100".encode('utf-8')
|
||||
}
|
||||
|
||||
rss_url = self.provider.url + 'rss?' + urllib.urlencode(params)
|
||||
|
||||
logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG)
|
||||
|
||||
return self.getRSSFeed(rss_url)
|
||||
|
||||
def _checkAuth(self, data):
|
||||
return self.provider._checkAuthFromData(data)
|
||||
|
||||
provider = Fanzub()
|
@ -54,6 +54,7 @@ class GenericProvider:
|
||||
self.show = None
|
||||
|
||||
self.supportsBacklog = False
|
||||
self.supportsAbsoluteNumbering = False
|
||||
|
||||
self.search_mode = None
|
||||
self.search_fallback = False
|
||||
@ -188,7 +189,7 @@ class GenericProvider:
|
||||
def searchRSS(self, episodes):
|
||||
return self.cache.findNeededEpisodes(episodes)
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
"""
|
||||
Figures out the quality of the given RSS item node
|
||||
|
||||
@ -197,7 +198,7 @@ class GenericProvider:
|
||||
Returns a Quality value obtained from the node's data
|
||||
"""
|
||||
(title, url) = self._get_title_and_url(item) # @UnusedVariable
|
||||
quality = Quality.sceneQuality(title)
|
||||
quality = Quality.sceneQuality(title, anime)
|
||||
return quality
|
||||
|
||||
def _doSearch(self, search_params, epcount=0, age=0):
|
||||
@ -282,16 +283,16 @@ class GenericProvider:
|
||||
|
||||
(title, url) = self._get_title_and_url(item)
|
||||
|
||||
quality = self.getQuality(item)
|
||||
|
||||
# parse the file name
|
||||
try:
|
||||
myParser = NameParser(False)
|
||||
myParser = NameParser(False, show=show, useIndexers=manualSearch)
|
||||
parse_result = myParser.parse(title)
|
||||
except InvalidNameException:
|
||||
logger.log(u"Unable to parse the filename " + title + " into a valid episode", logger.WARNING)
|
||||
continue
|
||||
|
||||
quality = self.getQuality(item, parse_result.is_anime)
|
||||
|
||||
# scene -> indexer numbering
|
||||
parse_result = parse_result.convert(self.show)
|
||||
|
||||
|
@ -79,7 +79,7 @@ class HDTorrentsProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'hdtorrents.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
return quality
|
||||
|
@ -71,9 +71,9 @@ class IPTorrentsProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'iptorrents.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _doLogin(self):
|
||||
|
@ -74,9 +74,9 @@ class KATProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'kat.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _reverseQuality(self, quality):
|
||||
|
@ -76,7 +76,7 @@ class NewzbinProvider(generic.NZBProvider):
|
||||
def isEnabled(self):
|
||||
return sickbeard.NEWZBIN
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
attributes = item.report[0]
|
||||
attr_dict = {}
|
||||
|
||||
|
@ -40,7 +40,7 @@ from sickbeard.exceptions import ex, AuthException
|
||||
|
||||
|
||||
class NewznabProvider(generic.NZBProvider):
|
||||
def __init__(self, name, url, key='', catIDs='5030,5040,5060', search_mode='eponly', search_fallback=False):
|
||||
def __init__(self, name, url, key='', catIDs='5030,5040', search_mode='eponly', search_fallback=False):
|
||||
|
||||
generic.NZBProvider.__init__(self, name)
|
||||
|
||||
@ -62,7 +62,7 @@ class NewznabProvider(generic.NZBProvider):
|
||||
if catIDs:
|
||||
self.catIDs = catIDs
|
||||
else:
|
||||
self.catIDs = '5030,5040, 5060'
|
||||
self.catIDs = '5030,5040'
|
||||
|
||||
self.enabled = True
|
||||
self.supportsBacklog = True
|
||||
@ -86,7 +86,8 @@ class NewznabProvider(generic.NZBProvider):
|
||||
to_return = []
|
||||
|
||||
# add new query strings for exceptions
|
||||
name_exceptions = scene_exceptions.get_scene_exceptions(self.show.indexerid) + [self.show.name]
|
||||
name_exceptions = scene_exceptions.get_scene_exceptions(self.show.indexerid, ep_obj.season) + [self.show.name]
|
||||
name_exceptions = set(name_exceptions)
|
||||
for cur_exception in name_exceptions:
|
||||
|
||||
cur_params = {}
|
||||
@ -95,7 +96,7 @@ class NewznabProvider(generic.NZBProvider):
|
||||
if ep_obj.show.indexer == 2:
|
||||
cur_params['rid'] = ep_obj.show.indexerid
|
||||
else:
|
||||
cur_params['q'] = helpers.sanitizeSceneName(cur_exception)
|
||||
cur_params['q'] = helpers.sanitizeSceneName(cur_exception).replace('.', '_')
|
||||
|
||||
# season
|
||||
if ep_obj.show.air_by_date or ep_obj.show.sports:
|
||||
@ -124,7 +125,7 @@ class NewznabProvider(generic.NZBProvider):
|
||||
if ep_obj.show.indexer == 2:
|
||||
params['rid'] = ep_obj.show.indexerid
|
||||
else:
|
||||
params['q'] = helpers.sanitizeSceneName(self.show.name)
|
||||
params['q'] = helpers.sanitizeSceneName(self.show.name).replace('.', '_')
|
||||
|
||||
if self.show.air_by_date or self.show.sports:
|
||||
date_str = str(ep_obj.airdate)
|
||||
@ -148,7 +149,7 @@ class NewznabProvider(generic.NZBProvider):
|
||||
continue
|
||||
|
||||
cur_return = params.copy()
|
||||
cur_return['q'] = helpers.sanitizeSceneName(cur_exception)
|
||||
cur_return['q'] = helpers.sanitizeSceneName(cur_exception).replace('.', '_')
|
||||
to_return.append(cur_return)
|
||||
|
||||
return to_return
|
||||
@ -185,6 +186,12 @@ class NewznabProvider(generic.NZBProvider):
|
||||
"limit": 100,
|
||||
"cat": self.catIDs}
|
||||
|
||||
# sports and anime catIDs
|
||||
if self.show.is_sports:
|
||||
params['cat'] += ',5060'
|
||||
elif self.show.is_anime:
|
||||
params['cat'] += ',5070,5090'
|
||||
|
||||
# if max_age is set, use it, don't allow it to be missing
|
||||
if age or not params['maxage']:
|
||||
params['maxage'] = age
|
||||
@ -288,6 +295,12 @@ class NewznabCache(tvcache.TVCache):
|
||||
params = {"t": "tvsearch",
|
||||
"cat": self.provider.catIDs}
|
||||
|
||||
# sports catIDs
|
||||
params['cat'] += ',5060'
|
||||
|
||||
# anime catIDs
|
||||
params['cat'] += ',5070,5090'
|
||||
|
||||
if self.provider.needs_auth and self.provider.key:
|
||||
params['apikey'] = self.provider.key
|
||||
|
||||
|
@ -77,9 +77,9 @@ class NextGenProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'nextgen.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def getLoginParams(self):
|
||||
|
@ -36,14 +36,14 @@ class NyaaProvider(generic.TorrentProvider):
|
||||
generic.TorrentProvider.__init__(self, "NyaaTorrents")
|
||||
|
||||
self.supportsBacklog = True
|
||||
|
||||
self.supportsAbsoluteNumbering = True
|
||||
|
||||
self.enabled = False
|
||||
self.ratio = None
|
||||
|
||||
self.cache = NyaaCache(self)
|
||||
|
||||
self.url = 'http://www.nyaa.eu/'
|
||||
self.url = 'http://www.nyaa.se/'
|
||||
|
||||
def isEnabled(self):
|
||||
return self.enabled
|
||||
@ -68,6 +68,9 @@ class NyaaProvider(generic.TorrentProvider):
|
||||
return self._get_season_search_strings(ep_obj)
|
||||
|
||||
def _doSearch(self, search_string, show=None, age=None):
|
||||
if self.show and not self.show.is_anime:
|
||||
logger.log(u"" + str(self.show.name) + " is not an anime skiping " + str(self.name))
|
||||
return []
|
||||
|
||||
params = {"term": search_string.encode('utf-8'),
|
||||
"sort": '2', #Sort Descending By Seeders
|
||||
@ -78,7 +81,7 @@ class NyaaProvider(generic.TorrentProvider):
|
||||
logger.log(u"Search string: " + searchURL, logger.DEBUG)
|
||||
|
||||
|
||||
data = self.getURL(searchURL)
|
||||
data = self.cache.getRSSFeed(searchURL)
|
||||
|
||||
if not data:
|
||||
logger.log(u"Error trying to load NyaaTorrents RSS feed: " + searchURL, logger.ERROR)
|
||||
@ -115,6 +118,8 @@ class NyaaProvider(generic.TorrentProvider):
|
||||
return match.group(1)
|
||||
return None
|
||||
|
||||
def seedRatio(self):
|
||||
return self.ratio
|
||||
|
||||
class NyaaCache(tvcache.TVCache):
|
||||
def __init__(self, provider):
|
||||
|
@ -97,7 +97,7 @@ class OmgwtfnzbsProvider(generic.NZBProvider):
|
||||
def _get_title_and_url(self, item):
|
||||
return (item['release'], item['getnzb'])
|
||||
|
||||
def _doSearch(self, search, show=None, retention=0):
|
||||
def _doSearch(self, search, epcount=0, retention=0):
|
||||
|
||||
self._checkAuth()
|
||||
|
||||
|
@ -73,9 +73,9 @@ class PublicHDProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'publichd.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _get_season_search_strings(self, ep_obj):
|
||||
|
@ -79,9 +79,9 @@ class SCCProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'scc.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _doLogin(self):
|
||||
|
@ -72,9 +72,9 @@ class SpeedCDProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'speedcd.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _doLogin(self):
|
||||
|
@ -73,9 +73,9 @@ class ThePirateBayProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'thepiratebay.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _reverseQuality(self, quality):
|
||||
|
@ -78,9 +78,9 @@ class TorrentDayProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'torrentday.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _doLogin(self):
|
||||
|
@ -73,9 +73,9 @@ class TorrentLeechProvider(generic.TorrentProvider):
|
||||
def imageName(self):
|
||||
return 'torrentleech.png'
|
||||
|
||||
def getQuality(self, item):
|
||||
def getQuality(self, item, anime=False):
|
||||
|
||||
quality = Quality.sceneQuality(item[0])
|
||||
quality = Quality.sceneQuality(item[0], anime)
|
||||
return quality
|
||||
|
||||
def _doLogin(self):
|
||||
|
@ -19,23 +19,66 @@
|
||||
import re
|
||||
import sickbeard
|
||||
|
||||
from lib import adba
|
||||
from sickbeard import helpers
|
||||
from sickbeard import name_cache
|
||||
from sickbeard import logger
|
||||
from sickbeard import db
|
||||
|
||||
exceptionCache = {}
|
||||
exceptionSeasonCache = {}
|
||||
exceptionIndexerCache = {}
|
||||
|
||||
def get_scene_exceptions(indexer_id):
|
||||
def get_scene_exceptions(indexer_id, season=-1):
|
||||
"""
|
||||
Given a indexer_id, return a list of all the scene exceptions.
|
||||
"""
|
||||
|
||||
myDB = db.DBConnection("cache.db")
|
||||
exceptions = myDB.select("SELECT show_name FROM scene_exceptions WHERE indexer_id = ?", [indexer_id])
|
||||
return [cur_exception["show_name"] for cur_exception in exceptions]
|
||||
global exceptionCache
|
||||
|
||||
if indexer_id not in exceptionCache or season not in exceptionCache[indexer_id]:
|
||||
myDB = db.DBConnection("cache.db")
|
||||
exceptions = myDB.select("SELECT show_name FROM scene_exceptions WHERE indexer_id = ? and season = ?", [indexer_id, season])
|
||||
exceptionsList = [cur_exception["show_name"] for cur_exception in exceptions]
|
||||
if not indexer_id in exceptionCache:
|
||||
exceptionCache[indexer_id] = {}
|
||||
exceptionCache[indexer_id][season] = exceptionsList
|
||||
else:
|
||||
exceptionsList = exceptionCache[indexer_id][season]
|
||||
|
||||
if season == 1: # if we where looking for season 1 we can add generic names
|
||||
exceptionsList += get_scene_exceptions(indexer_id, season=-1)
|
||||
|
||||
return exceptionsList
|
||||
|
||||
def get_all_scene_exceptions(indexer_id):
|
||||
myDB = db.DBConnection("cache.db")
|
||||
exceptions = myDB.select("SELECT show_name,season FROM scene_exceptions WHERE indexer_id = ?", [indexer_id])
|
||||
exceptionsList = {}
|
||||
[cur_exception["show_name"] for cur_exception in exceptions]
|
||||
for cur_exception in exceptions:
|
||||
if not cur_exception["season"] in exceptionsList:
|
||||
exceptionsList[cur_exception["season"]] = []
|
||||
exceptionsList[cur_exception["season"]].append(cur_exception["show_name"])
|
||||
|
||||
return exceptionsList
|
||||
|
||||
def get_scene_seasons(indexer_id):
|
||||
"""
|
||||
return a list of season numbers that have scene exceptions
|
||||
"""
|
||||
global exceptionSeasonCache
|
||||
if indexer_id not in exceptionSeasonCache:
|
||||
myDB = db.DBConnection("cache.db")
|
||||
sqlResults = myDB.select("SELECT DISTINCT(season) as season FROM scene_exceptions WHERE indexer_id = ?", [indexer_id])
|
||||
exceptionSeasonCache[indexer_id] = [int(x["season"]) for x in sqlResults]
|
||||
|
||||
return exceptionSeasonCache[indexer_id]
|
||||
|
||||
def get_scene_exception_by_name(show_name):
|
||||
return get_scene_exception_by_name_multiple(show_name)[0]
|
||||
|
||||
def get_scene_exception_by_name_multiple(show_name):
|
||||
"""
|
||||
Given a show name, return the indexerid of the exception, None if no exception
|
||||
is present.
|
||||
@ -44,24 +87,25 @@ def get_scene_exception_by_name(show_name):
|
||||
myDB = db.DBConnection("cache.db")
|
||||
|
||||
# try the obvious case first
|
||||
exception_result = myDB.select("SELECT indexer_id FROM scene_exceptions WHERE LOWER(show_name) = ?",
|
||||
[show_name.lower()])
|
||||
exception_result = myDB.select("SELECT indexer_id, season FROM scene_exceptions WHERE LOWER(show_name) = ? ORDER BY season ASC", [show_name.lower()])
|
||||
if exception_result:
|
||||
return int(exception_result[0]["indexer_id"])
|
||||
return [(int(x["indexer_id"]), int(x["season"])) for x in exception_result]
|
||||
|
||||
all_exception_results = myDB.select("SELECT show_name, indexer_id FROM scene_exceptions")
|
||||
out = []
|
||||
all_exception_results = myDB.select("SELECT show_name, indexer_id, season FROM scene_exceptions")
|
||||
for cur_exception in all_exception_results:
|
||||
|
||||
cur_exception_name = cur_exception["show_name"]
|
||||
cur_indexer_id = int(cur_exception["indexer_id"])
|
||||
cur_season = int(cur_exception["season"])
|
||||
|
||||
if show_name.lower() in (
|
||||
cur_exception_name.lower(), helpers.sanitizeSceneName(cur_exception_name).lower().replace('.', ' ')):
|
||||
logger.log(u"Scene exception lookup got Indexer ID " + str(cur_indexer_id) + u", using that", logger.DEBUG)
|
||||
return cur_indexer_id
|
||||
|
||||
return None
|
||||
|
||||
if show_name.lower() in (cur_exception_name.lower(), sickbeard.helpers.sanitizeSceneName(cur_exception_name).lower().replace('.', ' ')):
|
||||
logger.log(u"Scene exception lookup got indexer id " + str(cur_indexer_id) + u", using that", logger.DEBUG)
|
||||
out.append((cur_indexer_id, cur_season))
|
||||
if out:
|
||||
return out
|
||||
else:
|
||||
return [(None, None)]
|
||||
|
||||
def retrieve_exceptions():
|
||||
"""
|
||||
@ -69,6 +113,8 @@ def retrieve_exceptions():
|
||||
scene_exceptions table in cache.db. Also clears the scene name cache.
|
||||
"""
|
||||
|
||||
global exceptionCache, exceptionSeasonCache
|
||||
|
||||
exception_dict = {}
|
||||
|
||||
# exceptions are stored on github pages
|
||||
@ -97,10 +143,27 @@ def retrieve_exceptions():
|
||||
indexer_id = int(indexer_id)
|
||||
|
||||
# regex out the list of shows, taking \' into account
|
||||
alias_list = [re.sub(r'\\(.)', r'\1', x) for x in re.findall(r"'(.*?)(?<!\\)',?", aliases)]
|
||||
#alias_list = [re.sub(r'\\(.)', r'\1', x) for x in re.findall(r"'(.*?)(?<!\\)',?", aliases)]
|
||||
alias_list = [{re.sub(r'\\(.)', r'\1', x): -1} for x in re.findall(r"'(.*?)(?<!\\)',?", aliases)]
|
||||
|
||||
exception_dict[indexer_id] = alias_list
|
||||
|
||||
logger.log(u"Checking for XEM scene exception updates for " + sickbeard.indexerApi(indexer).name)
|
||||
xem_exceptions = _xem_excpetions_fetcher(indexer)
|
||||
for xem_ex in xem_exceptions: # anidb xml anime exceptions
|
||||
if xem_ex in exception_dict:
|
||||
exception_dict[xem_ex] = exception_dict[xem_ex] + xem_exceptions[xem_ex]
|
||||
else:
|
||||
exception_dict[xem_ex] = xem_exceptions[xem_ex]
|
||||
|
||||
logger.log(u"Checking for scene exception updates for AniDB")
|
||||
local_exceptions = _retrieve_anidb_mainnames()
|
||||
for local_ex in local_exceptions: # anidb xml anime exceptions
|
||||
if local_ex in exception_dict:
|
||||
exception_dict[local_ex] = exception_dict[local_ex] + local_exceptions[local_ex]
|
||||
else:
|
||||
exception_dict[local_ex] = local_exceptions[local_ex]
|
||||
|
||||
myDB = db.DBConnection("cache.db")
|
||||
|
||||
changed_exceptions = False
|
||||
@ -112,20 +175,25 @@ def retrieve_exceptions():
|
||||
existing_exceptions = [x["show_name"] for x in
|
||||
myDB.select("SELECT * FROM scene_exceptions WHERE indexer_id = ?", [cur_indexer_id])]
|
||||
|
||||
for cur_exception in exception_dict[cur_indexer_id]:
|
||||
for cur_exception_dict in exception_dict[cur_indexer_id]:
|
||||
cur_exception, curSeason = cur_exception_dict.items()[0]
|
||||
# if this exception isn't already in the DB then add it
|
||||
if cur_exception not in existing_exceptions:
|
||||
myDB.action("INSERT INTO scene_exceptions (indexer_id, show_name) VALUES (?,?)",
|
||||
[cur_indexer_id, cur_exception])
|
||||
myDB.action("INSERT INTO scene_exceptions (indexer_id, show_name, season) VALUES (?,?,?)",
|
||||
[cur_indexer_id, cur_exception, curSeason])
|
||||
changed_exceptions = True
|
||||
|
||||
# since this could invalidate the results of the cache we clear it out after updating
|
||||
if changed_exceptions:
|
||||
logger.log(u"Updated scene exceptions")
|
||||
name_cache.clearCache()
|
||||
exceptionCache = {}
|
||||
exceptionSeasonCache = {}
|
||||
else:
|
||||
logger.log(u"No scene exceptions update needed")
|
||||
|
||||
# update indexer cache
|
||||
updateIndexerCache()
|
||||
|
||||
def update_scene_exceptions(indexer_id, scene_exceptions):
|
||||
"""
|
||||
@ -139,4 +207,65 @@ def update_scene_exceptions(indexer_id, scene_exceptions):
|
||||
for cur_exception in scene_exceptions:
|
||||
myDB.action("INSERT INTO scene_exceptions (indexer_id, show_name) VALUES (?,?)", [indexer_id, cur_exception])
|
||||
|
||||
name_cache.clearCache()
|
||||
name_cache.clearCache()
|
||||
|
||||
def updateIndexerCache():
|
||||
logger.log(u"Updating internal scene name cache", logger.MESSAGE)
|
||||
_excpetionDots = []
|
||||
global exceptionIndexerCache
|
||||
exceptionIndexerCache = {}
|
||||
|
||||
for show in sickbeard.showList:
|
||||
for curSeason in [-1] + sickbeard.scene_exceptions.get_scene_seasons(show.indexerid):
|
||||
exceptionIndexerCache[helpers.full_sanitizeSceneName(show.name)] = show.indexerid
|
||||
_excpetionDots.append(".")
|
||||
for name in get_scene_exceptions(show.indexerid, season=curSeason):
|
||||
exceptionIndexerCache[name] = show.indexerid
|
||||
exceptionIndexerCache[helpers.full_sanitizeSceneName(name)] = show.indexerid
|
||||
_excpetionDots.append(".")
|
||||
|
||||
logger.log(u"Updated internal scene name cache " + "".join(_excpetionDots), logger.MESSAGE)
|
||||
logger.log(u"Internal scene name cache set to: " + str(exceptionIndexerCache), logger.DEBUG)
|
||||
|
||||
|
||||
def _retrieve_anidb_mainnames():
|
||||
|
||||
anidb_mainNames = {}
|
||||
for show in sickbeard.showList:
|
||||
if show.is_anime and show.indexer == 1:
|
||||
try:
|
||||
anime = adba.Anime(None, name=show.name, tvdbid=show.indexerid, autoCorrectName=True)
|
||||
except:
|
||||
continue
|
||||
else:
|
||||
if anime.name and anime.name != show.name:
|
||||
anidb_mainNames[show.indexerid] = [{anime.name:-1}]
|
||||
|
||||
#logger.log("anidb anime names: " + str(anidb_mainNames), logger.DEBUG)
|
||||
return anidb_mainNames
|
||||
|
||||
def _xem_excpetions_fetcher(indexer):
|
||||
exception_dict = {}
|
||||
|
||||
url = "http://thexem.de/map/allNames?origin=%s&seasonNumbers=1" % sickbeard.indexerApi(indexer).config['xem_origin']
|
||||
|
||||
url_data = helpers.getURL(url, json=True)
|
||||
if url_data is None:
|
||||
logger.log(u"Check scene exceptions update failed. Unable to get URL: " + url, logger.ERROR)
|
||||
return exception_dict
|
||||
|
||||
if url_data['result'] == 'failure':
|
||||
return exception_dict
|
||||
|
||||
for indexerid, names in url_data['data'].items():
|
||||
exception_dict[int(indexerid)] = names
|
||||
|
||||
#logger.log(u"xem exception dict: " + str(exception_dict), logger.DEBUG)
|
||||
return exception_dict
|
||||
|
||||
def getSceneSeasons(indexer_id):
|
||||
"""get a list of season numbers that have scene excpetions
|
||||
"""
|
||||
myDB = db.DBConnection("cache.db")
|
||||
seasons = myDB.select("SELECT DISTINCT season FROM scene_exceptions WHERE indexer_id = ?", [indexer_id])
|
||||
return [cur_exception["season"] for cur_exception in seasons]
|
||||
|
@ -39,7 +39,7 @@ from lib import requests
|
||||
MAX_XEM_AGE_SECS = 86400 # 1 day
|
||||
|
||||
|
||||
def get_scene_numbering(indexer_id, indexer, season, episode, fallback_to_xem=True):
|
||||
def get_scene_numbering(indexer_id, indexer, season, episode, absolute_number=None, fallback_to_xem=True):
|
||||
"""
|
||||
Returns a tuple, (season, episode), with the scene numbering (if there is one),
|
||||
otherwise returns the xem numbering (if fallback_to_xem is set), otherwise
|
||||
@ -53,28 +53,28 @@ def get_scene_numbering(indexer_id, indexer, season, episode, fallback_to_xem=Tr
|
||||
@return: (int, int) a tuple with (season, episode)
|
||||
"""
|
||||
if indexer_id is None or season is None or episode is None:
|
||||
return (season, episode)
|
||||
return (season, episode, absolute_number)
|
||||
|
||||
indexer_id = int(indexer_id)
|
||||
indexer = int(indexer)
|
||||
|
||||
result = find_scene_numbering(indexer_id, indexer, season, episode)
|
||||
result = find_scene_numbering(indexer_id, indexer, season, episode, absolute_number)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
if fallback_to_xem:
|
||||
xem_result = find_xem_numbering(indexer_id, indexer, season, episode)
|
||||
xem_result = find_xem_numbering(indexer_id, indexer, season, episode, absolute_number)
|
||||
if xem_result:
|
||||
return xem_result
|
||||
return (season, episode)
|
||||
return (season, episode, absolute_number)
|
||||
|
||||
|
||||
def find_scene_numbering(indexer_id, indexer, season, episode):
|
||||
def find_scene_numbering(indexer_id, indexer, season, episode, absolute_number=None):
|
||||
"""
|
||||
Same as get_scene_numbering(), but returns None if scene numbering is not set
|
||||
"""
|
||||
if indexer_id is None or season is None or episode is None:
|
||||
return (season, episode)
|
||||
return (season, episode, absolute_number)
|
||||
|
||||
indexer_id = int(indexer_id)
|
||||
indexer = int(indexer)
|
||||
@ -82,13 +82,13 @@ def find_scene_numbering(indexer_id, indexer, season, episode):
|
||||
myDB = db.DBConnection()
|
||||
|
||||
rows = myDB.select(
|
||||
"SELECT scene_season, scene_episode FROM scene_numbering WHERE indexer = ? and indexer_id = ? and season = ? and episode = ?",
|
||||
"SELECT scene_season, scene_episode, scene_absolute_number FROM scene_numbering WHERE indexer = ? and indexer_id = ? and season = ? and episode = ?",
|
||||
[indexer, indexer_id, season, episode])
|
||||
if rows:
|
||||
return (int(rows[0]["scene_season"]), int(rows[0]["scene_episode"]))
|
||||
return (int(rows[0]["scene_season"]), int(rows[0]["scene_episode"]), int(rows[0]["scene_absolute_number"]))
|
||||
|
||||
|
||||
def get_indexer_numbering(indexer_id, indexer, sceneSeason, sceneEpisode, fallback_to_xem=True):
|
||||
def get_indexer_numbering(indexer_id, indexer, sceneSeason, sceneEpisode, sceneAbsoluteNumber=None, fallback_to_xem=True):
|
||||
"""
|
||||
Returns a tuple, (season, episode) with the TVDB and TVRAGE numbering for (sceneSeason, sceneEpisode)
|
||||
(this works like the reverse of get_scene_numbering)
|
||||
@ -102,14 +102,14 @@ def get_indexer_numbering(indexer_id, indexer, sceneSeason, sceneEpisode, fallba
|
||||
myDB = db.DBConnection()
|
||||
|
||||
rows = myDB.select(
|
||||
"SELECT season, episode FROM scene_numbering WHERE indexer = ? and indexer_id = ? and scene_season = ? and scene_episode = ?",
|
||||
"SELECT season, episode, absolute_number FROM scene_numbering WHERE indexer = ? and indexer_id = ? and scene_season = ? and scene_episode = ?",
|
||||
[indexer, indexer_id, sceneSeason, sceneEpisode])
|
||||
if rows:
|
||||
return (int(rows[0]["season"]), int(rows[0]["episode"]))
|
||||
return (int(rows[0]["season"]), int(rows[0]["episode"]), int(rows[0]["absolute_number"]))
|
||||
else:
|
||||
if fallback_to_xem:
|
||||
return get_indexer_numbering_for_xem(indexer_id, indexer, sceneSeason, sceneEpisode)
|
||||
return (sceneSeason, sceneEpisode)
|
||||
return get_indexer_numbering_for_xem(indexer_id, indexer, sceneSeason, sceneEpisode, sceneAbsoluteNumber)
|
||||
return (sceneSeason, sceneEpisode, sceneAbsoluteNumber)
|
||||
|
||||
|
||||
def get_scene_numbering_for_show(indexer_id, indexer):
|
||||
@ -127,16 +127,27 @@ def get_scene_numbering_for_show(indexer_id, indexer):
|
||||
myDB = db.DBConnection()
|
||||
|
||||
rows = myDB.select(
|
||||
'SELECT season, episode, scene_season, scene_episode FROM scene_numbering WHERE indexer = ? and indexer_id = ? ORDER BY season, episode',
|
||||
'SELECT season, episode, absolute_number, scene_season, scene_episode, scene_absolute_number FROM scene_numbering WHERE indexer = ? and indexer_id = ? ORDER BY season, episode',
|
||||
[indexer, indexer_id])
|
||||
|
||||
result = {}
|
||||
for row in rows:
|
||||
result[(int(row['season']), int(row['episode']))] = (int(row['scene_season']), int(row['scene_episode']))
|
||||
season = int(row['season'])
|
||||
episode = int(row['episode'])
|
||||
scene_season = int(row['scene_season'])
|
||||
scene_episode = int(row['scene_episode'])
|
||||
scene_absolute_number = int(row['scene_absolute_number'])
|
||||
|
||||
try:
|
||||
result[(season, episode)]
|
||||
except:
|
||||
result[(season, episode)] = (scene_season, scene_episode, scene_absolute_number)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def set_scene_numbering(indexer_id, indexer, season, episode, sceneSeason=None, sceneEpisode=None):
|
||||
def set_scene_numbering(indexer_id, indexer, season, episode, absolute_number, sceneSeason=None, sceneEpisode=None,
|
||||
sceneAbsoluteNumber=None):
|
||||
"""
|
||||
Set scene numbering for a season/episode.
|
||||
To clear the scene numbering, leave both sceneSeason and sceneEpisode as None.
|
||||
@ -151,7 +162,7 @@ def set_scene_numbering(indexer_id, indexer, season, episode, sceneSeason=None,
|
||||
myDB = db.DBConnection()
|
||||
|
||||
# sanity
|
||||
#if sceneSeason == None: sceneSeason = season
|
||||
# if sceneSeason == None: sceneSeason = season
|
||||
#if sceneEpisode == None: sceneEpisode = episode
|
||||
|
||||
# delete any existing record first
|
||||
@ -161,11 +172,11 @@ def set_scene_numbering(indexer_id, indexer, season, episode, sceneSeason=None,
|
||||
# now, if the new numbering is not the default, we save a new record
|
||||
if sceneSeason is not None and sceneEpisode is not None:
|
||||
myDB.action(
|
||||
"INSERT INTO scene_numbering (indexer, indexer_id, season, episode, scene_season, scene_episode) VALUES (?,?,?,?,?,?)",
|
||||
[indexer, indexer_id, season, episode, sceneSeason, sceneEpisode])
|
||||
"INSERT INTO scene_numbering (indexer, indexer_id, season, episode, absolute_number, scene_season, scene_episode, scene_absolute_number) VALUES (?,?,?,?,?,?,?,?)",
|
||||
[indexer, indexer_id, season, episode, absolute_number, sceneSeason, sceneEpisode, sceneAbsoluteNumber])
|
||||
|
||||
|
||||
def find_xem_numbering(indexer_id, indexer, season, episode):
|
||||
def find_xem_numbering(indexer_id, indexer, season, episode, absolute_number):
|
||||
"""
|
||||
Returns the scene numbering, as retrieved from xem.
|
||||
Refreshes/Loads as needed.
|
||||
@ -176,7 +187,7 @@ def find_xem_numbering(indexer_id, indexer, season, episode):
|
||||
@return: (int, int) a tuple of scene_season, scene_episode, or None if there is no special mapping.
|
||||
"""
|
||||
if indexer_id is None or season is None or episode is None:
|
||||
return None
|
||||
return (season, episode, absolute_number)
|
||||
|
||||
indexer_id = int(indexer_id)
|
||||
indexer = int(indexer)
|
||||
@ -185,29 +196,23 @@ def find_xem_numbering(indexer_id, indexer, season, episode):
|
||||
_xem_refresh(indexer_id, indexer)
|
||||
|
||||
cacheDB = db.DBConnection('cache.db')
|
||||
myDB = db.DBConnection()
|
||||
|
||||
rows = cacheDB.select(
|
||||
"SELECT scene_season, scene_episode FROM xem_numbering WHERE indexer = ? and indexer_id = ? and season = ? and episode = ?",
|
||||
"SELECT scene_season, scene_episode, scene_absolute_number FROM xem_numbering WHERE indexer = ? and indexer_id = ? and season = ? and episode = ?",
|
||||
[indexer, indexer_id, season, episode])
|
||||
|
||||
scene_seasons = cacheDB.select(
|
||||
"SELECT COUNT(DISTINCT scene_season) as count FROM xem_numbering WHERE indexer = ? and indexer_id = ?",
|
||||
[indexer, indexer_id])
|
||||
|
||||
indexer_seasons = myDB.select(
|
||||
"SELECT COUNT(DISTINCT season) as count FROM tv_episodes WHERE indexer = ? and showid = ?",
|
||||
[indexer, indexer_id])
|
||||
|
||||
if rows:
|
||||
return (int(rows[0]["scene_season"]), int(rows[0]["scene_episode"]))
|
||||
elif int(scene_seasons[0]["count"]) > 0 and int(indexer_seasons[0]["count"]) > int(scene_seasons[0]["count"]):
|
||||
return (0,0)
|
||||
return (int(rows[0]["scene_season"]), int(rows[0]["scene_episode"]), int(rows[0]["scene_absolute_number"]))
|
||||
elif cacheDB.select(
|
||||
"SELECT * FROM xem_numbering WHERE indexer = ? and indexer_id = ?",
|
||||
[indexer, indexer_id]):
|
||||
|
||||
return (0, 0, 0)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_indexer_numbering_for_xem(indexer_id, indexer, sceneSeason, sceneEpisode):
|
||||
def get_indexer_numbering_for_xem(indexer_id, indexer, sceneSeason, sceneEpisode, sceneAbsoluteNumber):
|
||||
"""
|
||||
Reverse of find_xem_numbering: lookup a tvdb season and episode using scene numbering
|
||||
|
||||
@ -226,12 +231,12 @@ def get_indexer_numbering_for_xem(indexer_id, indexer, sceneSeason, sceneEpisode
|
||||
_xem_refresh(indexer_id, indexer)
|
||||
cacheDB = db.DBConnection('cache.db')
|
||||
rows = cacheDB.select(
|
||||
"SELECT season, episode FROM xem_numbering WHERE indexer = ? and indexer_id = ? and scene_season = ? and scene_episode = ?",
|
||||
"SELECT season, episode, absolute_number FROM xem_numbering WHERE indexer = ? and indexer_id = ? and scene_season = ? and scene_episode = ?",
|
||||
[indexer, indexer_id, sceneSeason, sceneEpisode])
|
||||
if rows:
|
||||
return (int(rows[0]["season"]), int(rows[0]["episode"]))
|
||||
return (int(rows[0]["season"]), int(rows[0]["episode"]), int(rows[0]["absolute_number"]))
|
||||
else:
|
||||
return (sceneSeason, sceneEpisode)
|
||||
return (sceneSeason, sceneEpisode, sceneAbsoluteNumber)
|
||||
|
||||
|
||||
def _xem_refresh_needed(indexer_id, indexer):
|
||||
@ -286,27 +291,35 @@ def _xem_refresh(indexer_id, indexer):
|
||||
return None
|
||||
|
||||
result = data
|
||||
ql = []
|
||||
|
||||
cacheDB = db.DBConnection('cache.db')
|
||||
|
||||
ql = []
|
||||
if result:
|
||||
ql.append(["INSERT OR REPLACE INTO xem_refresh (indexer, indexer_id, last_refreshed) VALUES (?,?,?)",
|
||||
[indexer, indexer_id, time.time()]])
|
||||
[indexer, indexer_id, time.time()]])
|
||||
if 'success' in result['result']:
|
||||
ql.append(["DELETE FROM xem_numbering where indexer = ? and indexer_id = ?", [indexer, indexer_id]])
|
||||
for entry in result['data']:
|
||||
if 'scene' in entry:
|
||||
ql.append([
|
||||
"INSERT INTO xem_numbering (indexer, indexer_id, season, episode, scene_season, scene_episode) VALUES (?,?,?,?,?,?)",
|
||||
[indexer, indexer_id, entry[sickbeard.indexerApi(indexer).config['xem_origin']]['season'],
|
||||
"INSERT OR IGNORE INTO xem_numbering (indexer, indexer_id, season, episode, absolute_number, scene_season, scene_episode, scene_absolute_number) VALUES (?,?,?,?,?,?,?,?)",
|
||||
[indexer, indexer_id,
|
||||
entry[sickbeard.indexerApi(indexer).config['xem_origin']]['season'],
|
||||
entry[sickbeard.indexerApi(indexer).config['xem_origin']]['episode'],
|
||||
entry['scene']['season'], entry['scene']['episode']]])
|
||||
entry[sickbeard.indexerApi(indexer).config['xem_origin']]['absolute'],
|
||||
entry['scene']['season'],
|
||||
entry['scene']['episode'],
|
||||
entry['scene']['absolute']]])
|
||||
if 'scene_2' in entry: # for doubles
|
||||
ql.append([
|
||||
"INSERT INTO xem_numbering (indexer, indexer_id, season, episode, scene_season, scene_episode) VALUES (?,?,?,?,?,?)",
|
||||
[indexer, indexer_id, entry[sickbeard.indexerApi(indexer).config['xem_origin']]['season'],
|
||||
"INSERT OR IGNORE INTO xem_numbering (indexer, indexer_id, season, episode, absolute_number, scene_season, scene_episode, scene_absolute_number) VALUES (?,?,?,?,?,?,?,?)",
|
||||
[indexer, indexer_id,
|
||||
entry[sickbeard.indexerApi(indexer).config['xem_origin']]['season'],
|
||||
entry[sickbeard.indexerApi(indexer).config['xem_origin']]['episode'],
|
||||
entry['scene_2']['season'], entry['scene_2']['episode']]])
|
||||
entry[sickbeard.indexerApi(indexer).config['xem_origin']]['absolute'],
|
||||
entry['scene_2']['season'],
|
||||
entry['scene_2']['episode'],
|
||||
entry['scene_2']['absolute']]])
|
||||
else:
|
||||
logger.log(u'Failed to get XEM scene data for show %s from %s because "%s"' % (
|
||||
indexer_id, sickbeard.indexerApi(indexer).name, result['message']), logger.DEBUG)
|
||||
@ -322,6 +335,7 @@ def _xem_refresh(indexer_id, indexer):
|
||||
if ql:
|
||||
cacheDB.mass_action(ql)
|
||||
|
||||
|
||||
def get_xem_numbering_for_show(indexer_id, indexer):
|
||||
"""
|
||||
Returns a dict of (season, episode) : (sceneSeason, sceneEpisode) mappings
|
||||
@ -340,12 +354,18 @@ def get_xem_numbering_for_show(indexer_id, indexer):
|
||||
cacheDB = db.DBConnection('cache.db')
|
||||
|
||||
rows = cacheDB.select(
|
||||
'SELECT season, episode, scene_season, scene_episode FROM xem_numbering WHERE indexer = ? and indexer_id = ? ORDER BY season, episode',
|
||||
'SELECT season, episode, absolute_number, scene_season, scene_episode, scene_absolute_number FROM xem_numbering WHERE indexer = ? and indexer_id = ? ORDER BY season, episode',
|
||||
[indexer, indexer_id])
|
||||
|
||||
result = {}
|
||||
for row in rows:
|
||||
result[(int(row['season']), int(row['episode']))] = (int(row['scene_season']), int(row['scene_episode']))
|
||||
season = int(row['season'])
|
||||
episode = int(row['episode'])
|
||||
scene_season = int(row['scene_season'])
|
||||
scene_episode = int(row['scene_episode'])
|
||||
scene_absolute_number = int(row['scene_absolute_number'])
|
||||
|
||||
result[(season, episode)] = (scene_season, scene_episode, scene_absolute_number)
|
||||
|
||||
return result
|
||||
|
||||
@ -368,7 +388,8 @@ def get_xem_numbering_for_season(indexer_id, indexer, season):
|
||||
cacheDB = db.DBConnection('cache.db')
|
||||
|
||||
rows = cacheDB.select(
|
||||
'SELECT season, scene_season FROM xem_numbering WHERE indexer = ? and indexer_id = ? AND season = ? ORDER BY season', [indexer, indexer_id, season])
|
||||
'SELECT season, scene_season FROM xem_numbering WHERE indexer = ? and indexer_id = ? AND season = ? ORDER BY season',
|
||||
[indexer, indexer_id, season])
|
||||
|
||||
result = {}
|
||||
if rows:
|
||||
@ -389,25 +410,29 @@ def fix_scene_numbering():
|
||||
"SELECT showid, indexerid, indexer, episode_id, season, episode FROM tv_episodes WHERE scene_season = -1 OR scene_episode = -1")
|
||||
|
||||
for epResult in sqlResults:
|
||||
|
||||
indexerid = int(epResult["showid"])
|
||||
indexer = int(epResult["indexer"])
|
||||
season = int(epResult["season"])
|
||||
episode = int(epResult["episode"])
|
||||
absolute_number = int(epResult["absolute_number"])
|
||||
|
||||
logger.log(
|
||||
u"Repairing any scene numbering issues for showid: " + str(epResult["showid"]) + u" season: " + str(
|
||||
epResult["season"]) + u" episode: " + str(epResult["episode"]), logger.DEBUG)
|
||||
|
||||
scene_season, scene_episode = sickbeard.scene_numbering.get_scene_numbering(indexerid,
|
||||
indexer,
|
||||
season,
|
||||
episode)
|
||||
scene_season, scene_episode, scene_absolute_number = sickbeard.scene_numbering.get_scene_numbering(indexerid,
|
||||
indexer,
|
||||
season,
|
||||
episode,
|
||||
absolute_number)
|
||||
|
||||
ql.append(
|
||||
["UPDATE tv_episodes SET scene_season = ? WHERE indexerid = ?", [scene_season, epResult["indexerid"]]])
|
||||
ql.append(
|
||||
["UPDATE tv_episodes SET scene_episode = ? WHERE indexerid = ?", [scene_episode, epResult["indexerid"]]])
|
||||
ql.append(
|
||||
["UPDATE tv_episodes SET scene_absolute_number = ? WHERE indexerid = ?",
|
||||
[scene_absolute_number, epResult["indexerid"]]])
|
||||
|
||||
if ql:
|
||||
myDB.mass_action(ql)
|
||||
|
@ -141,7 +141,7 @@ def snatchEpisode(result, endStatus=SNATCHED):
|
||||
if sickbeard.TORRENT_METHOD == "blackhole":
|
||||
dlResult = _downloadResult(result)
|
||||
else:
|
||||
#Sets per provider seed ratio
|
||||
# Sets per provider seed ratio
|
||||
result.ratio = result.provider.seedRatio()
|
||||
result.content = result.provider.getURL(result.url) if not result.url.startswith('magnet') else None
|
||||
client = clients.getClientIstance(sickbeard.TORRENT_METHOD)()
|
||||
@ -298,14 +298,15 @@ def isFirstBestMatch(result):
|
||||
return False
|
||||
|
||||
|
||||
def filterSearchResults(show, results):
|
||||
def filterSearchResults(show, season, results):
|
||||
foundResults = {}
|
||||
|
||||
# make a list of all the results for this provider
|
||||
for curEp in results:
|
||||
# skip non-tv crap
|
||||
results[curEp] = filter(
|
||||
lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name, show),
|
||||
lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name, show,
|
||||
season=season),
|
||||
results[curEp])
|
||||
|
||||
if curEp in foundResults:
|
||||
@ -354,12 +355,6 @@ def searchForNeededEpisodes(episodes):
|
||||
logger.DEBUG)
|
||||
continue
|
||||
|
||||
# find the best result for the current episode
|
||||
#bestResult = None
|
||||
#for curResult in curFoundResults[curEp]:
|
||||
# if not bestResult or bestResult.quality < curResult.quality:
|
||||
# bestResult = curResult
|
||||
|
||||
bestResult = pickBestResult(curFoundResults[curEp], curEp.show)
|
||||
|
||||
# if all results were rejected move on to the next episode
|
||||
@ -408,7 +403,7 @@ def searchProviders(show, season, episodes, manualSearch=False):
|
||||
if seasonSearch and provider.search_mode == 'sponly':
|
||||
search_mode = provider.search_mode
|
||||
|
||||
while(True):
|
||||
while (True):
|
||||
searchCount += 1
|
||||
|
||||
if search_mode == 'sponly':
|
||||
@ -426,7 +421,20 @@ def searchProviders(show, season, episodes, manualSearch=False):
|
||||
break
|
||||
|
||||
if len(searchResults):
|
||||
foundResults[provider.name] = filterSearchResults(show, searchResults)
|
||||
# make a list of all the results for this provider
|
||||
for curEp in searchResults:
|
||||
# skip non-tv crap
|
||||
searchResults[curEp] = filter(
|
||||
lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name,
|
||||
show,
|
||||
season=season),
|
||||
searchResults[curEp])
|
||||
|
||||
if curEp in foundResults:
|
||||
foundResults[provider.name][curEp] += searchResults[curEp]
|
||||
else:
|
||||
foundResults[provider.name][curEp] = searchResults[curEp]
|
||||
|
||||
break
|
||||
elif not provider.search_fallback or searchCount == 2:
|
||||
break
|
||||
@ -444,7 +452,6 @@ def searchProviders(show, season, episodes, manualSearch=False):
|
||||
continue
|
||||
break
|
||||
|
||||
|
||||
anyQualities, bestQualities = Quality.splitQuality(show.quality)
|
||||
|
||||
# pick the best season NZB
|
||||
@ -511,7 +518,8 @@ def searchProviders(show, season, episodes, manualSearch=False):
|
||||
|
||||
individualResults = filter(
|
||||
lambda x: show_name_helpers.filterBadReleases(x.name) and show_name_helpers.isGoodResult(x.name,
|
||||
show),
|
||||
show,
|
||||
season=season),
|
||||
individualResults)
|
||||
|
||||
for curResult in individualResults:
|
||||
@ -563,7 +571,7 @@ def searchProviders(show, season, episodes, manualSearch=False):
|
||||
if epNum in foundResults[provider.name] and len(foundResults[provider.name][epNum]) > 0:
|
||||
# but the multi-ep is worse quality, we don't want it
|
||||
# TODO: wtf is this False for
|
||||
#if False and multiResult.quality <= pickBestResult(foundResults[epNum]):
|
||||
# if False and multiResult.quality <= pickBestResult(foundResults[epNum]):
|
||||
# notNeededEps.append(epNum)
|
||||
#else:
|
||||
neededEps.append(epNum)
|
||||
|
@ -22,7 +22,7 @@ import re
|
||||
import datetime
|
||||
|
||||
import sickbeard
|
||||
from sickbeard.common import countryList
|
||||
from sickbeard import common
|
||||
from sickbeard.helpers import sanitizeSceneName
|
||||
from sickbeard.scene_exceptions import get_scene_exceptions
|
||||
from sickbeard import logger
|
||||
@ -90,7 +90,7 @@ def sceneToNormalShowNames(name):
|
||||
results.append(re.sub('(\D)(\d{4})$', '\\1(\\2)', cur_name))
|
||||
|
||||
# add brackets around the country
|
||||
country_match_str = '|'.join(countryList.values())
|
||||
country_match_str = '|'.join(common.countryList.values())
|
||||
results.append(re.sub('(?i)([. _-])(' + country_match_str + ')$', '\\1(\\2)', cur_name))
|
||||
|
||||
results += name_list
|
||||
@ -98,8 +98,8 @@ def sceneToNormalShowNames(name):
|
||||
return list(set(results))
|
||||
|
||||
|
||||
def makeSceneShowSearchStrings(show):
|
||||
showNames = allPossibleShowNames(show)
|
||||
def makeSceneShowSearchStrings(show, season=-1):
|
||||
showNames = allPossibleShowNames(show, season=season)
|
||||
|
||||
# scenify the names
|
||||
return map(sanitizeSceneName, showNames)
|
||||
@ -112,19 +112,43 @@ def makeSceneSeasonSearchString(show, ep_obj, extraSearchType=None):
|
||||
numseasons = 0
|
||||
|
||||
# the search string for air by date shows is just
|
||||
seasonStrings = [str(ep_obj.airdate).split('-')[0]]
|
||||
elif show.is_anime:
|
||||
numseasons = 0
|
||||
seasonEps = show.getAllEpisodes(ep_obj.season)
|
||||
|
||||
# get show qualities
|
||||
anyQualities, bestQualities = common.Quality.splitQuality(show.quality)
|
||||
|
||||
# compile a list of all the episode numbers we need in this 'season'
|
||||
seasonStrings = []
|
||||
for episode in seasonEps:
|
||||
|
||||
# get quality of the episode
|
||||
curCompositeStatus = episode.status
|
||||
curStatus, curQuality = common.Quality.splitCompositeStatus(curCompositeStatus)
|
||||
|
||||
if bestQualities:
|
||||
highestBestQuality = max(bestQualities)
|
||||
else:
|
||||
highestBestQuality = 0
|
||||
|
||||
# if we need a better one then add it to the list of episodes to fetch
|
||||
if (curStatus in (
|
||||
common.DOWNLOADED, common.SNATCHED) and curQuality < highestBestQuality) or curStatus == common.WANTED:
|
||||
ab_number = episode.scene_absolute_number
|
||||
if ab_number > 0:
|
||||
seasonStrings.append("%d" % ab_number)
|
||||
|
||||
else:
|
||||
numseasonsSQlResult = myDB.select(
|
||||
"SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0",
|
||||
[show.indexerid])
|
||||
|
||||
numseasons = int(numseasonsSQlResult[0][0])
|
||||
seasonStrings = ["S%02d" % int(ep_obj.scene_season)]
|
||||
|
||||
if show.air_by_date or show.sports:
|
||||
seasonStrings = [str(ep_obj.airdate).split('-')[0]]
|
||||
else:
|
||||
seasonStrings = ["S%02d" % int(ep_obj.scene_season)]
|
||||
|
||||
showNames = set(makeSceneShowSearchStrings(show))
|
||||
showNames = set(makeSceneShowSearchStrings(show, ep_obj.scene_season))
|
||||
|
||||
toReturn = []
|
||||
|
||||
@ -140,9 +164,6 @@ def makeSceneSeasonSearchString(show, ep_obj, extraSearchType=None):
|
||||
for cur_season in seasonStrings:
|
||||
toReturn.append(curShow + "." + cur_season)
|
||||
|
||||
# episode
|
||||
toReturn.extend(makeSceneSearchString(show, ep_obj))
|
||||
|
||||
return toReturn
|
||||
|
||||
|
||||
@ -152,26 +173,22 @@ def makeSceneSearchString(show, ep_obj):
|
||||
"SELECT COUNT(DISTINCT season) as numseasons FROM tv_episodes WHERE showid = ? and season != 0",
|
||||
[show.indexerid])
|
||||
numseasons = int(numseasonsSQlResult[0][0])
|
||||
numepisodesSQlResult = myDB.select(
|
||||
"SELECT COUNT(episode) as numepisodes FROM tv_episodes WHERE showid = ? and season != 0",
|
||||
[show.indexerid])
|
||||
numepisodes = int(numepisodesSQlResult[0][0])
|
||||
|
||||
# see if we should use dates instead of episodes
|
||||
if show.air_by_date and ep_obj.airdate != datetime.date.fromordinal(1):
|
||||
epStrings = [str(ep_obj.airdate)]
|
||||
elif show.sports:
|
||||
if (show.air_by_date or show.sports) and ep_obj.airdate != datetime.date.fromordinal(1):
|
||||
epStrings = [str(ep_obj.airdate)]
|
||||
elif show.is_anime:
|
||||
epStrings = ["%i" % int(ep_obj.scene_absolute_number)]
|
||||
else:
|
||||
epStrings = ["S%02iE%02i" % (int(ep_obj.scene_season), int(ep_obj.scene_episode)),
|
||||
"%ix%02i" % (int(ep_obj.scene_season), int(ep_obj.scene_episode))]
|
||||
|
||||
# for single-season shows just search for the show name -- if total ep count (exclude s0) is less than 11
|
||||
# due to the amount of qualities and releases, it is easy to go over the 50 result limit on rss feeds otherwise
|
||||
if numseasons == 1 and numepisodes < 11:
|
||||
if numseasons == 1 and not ep_obj.show.is_anime:
|
||||
epStrings = ['']
|
||||
|
||||
showNames = set(makeSceneShowSearchStrings(show))
|
||||
showNames = set(makeSceneShowSearchStrings(show, ep_obj.scene_season))
|
||||
|
||||
toReturn = []
|
||||
|
||||
@ -182,20 +199,26 @@ def makeSceneSearchString(show, ep_obj):
|
||||
return toReturn
|
||||
|
||||
|
||||
def isGoodResult(name, show, log=True):
|
||||
def isGoodResult(name, show, log=True, season=-1):
|
||||
"""
|
||||
Use an automatically-created regex to make sure the result actually is the show it claims to be
|
||||
"""
|
||||
|
||||
all_show_names = allPossibleShowNames(show)
|
||||
all_show_names = allPossibleShowNames(show, season=season)
|
||||
showNames = map(sanitizeSceneName, all_show_names) + all_show_names
|
||||
showNames += map(unidecode, all_show_names)
|
||||
|
||||
for curName in set(showNames):
|
||||
escaped_name = re.sub('\\\\[\\s.-]', '\W+', re.escape(curName))
|
||||
if show.startyear:
|
||||
escaped_name += "(?:\W+" + str(show.startyear) + ")?"
|
||||
curRegex = '^' + escaped_name + '\W+(?:(?:S\d[\dE._ -])|(?:\d\d?x)|(?:\d{4}\W\d\d\W\d\d)|(?:(?:part|pt)[\._ -]?(\d|[ivx]))|Season\W+\d+\W+|E\d+\W+|(?:\d{1,3}.+\d{1,}[a-zA-Z]{2}\W+[a-zA-Z]{3,}\W+\d{4}.+))'
|
||||
if not show.is_anime:
|
||||
escaped_name = re.sub('\\\\[\\s.-]', '\W+', re.escape(curName))
|
||||
if show.startyear:
|
||||
escaped_name += "(?:\W+" + str(show.startyear) + ")?"
|
||||
curRegex = '^' + escaped_name + '\W+(?:(?:S\d[\dE._ -])|(?:\d\d?x)|(?:\d{4}\W\d\d\W\d\d)|(?:(?:part|pt)[\._ -]?(\d|[ivx]))|Season\W+\d+\W+|E\d+\W+|(?:\d{1,3}.+\d{1,}[a-zA-Z]{2}\W+[a-zA-Z]{3,}\W+\d{4}.+))'
|
||||
else:
|
||||
escaped_name = re.sub('\\\\[\\s.-]', '[\W_]+', re.escape(curName))
|
||||
# FIXME: find a "automatically-created" regex for anime releases # test at http://regexr.com?2uon3
|
||||
curRegex = '^((\[.*?\])|(\d+[\.-]))*[ _\.]*' + escaped_name + '(([ ._-]+\d+)|([ ._-]+s\d{2})).*'
|
||||
|
||||
if log:
|
||||
logger.log(u"Checking if show " + name + " matches " + curRegex, logger.DEBUG)
|
||||
|
||||
@ -209,7 +232,7 @@ def isGoodResult(name, show, log=True):
|
||||
return False
|
||||
|
||||
|
||||
def allPossibleShowNames(show):
|
||||
def allPossibleShowNames(show, season=-1):
|
||||
"""
|
||||
Figures out every possible variation of the name for a particular show. Includes TVDB name, TVRage name,
|
||||
country codes on the end, eg. "Show Name (AU)", and any scene exception names.
|
||||
@ -219,27 +242,33 @@ def allPossibleShowNames(show):
|
||||
Returns: a list of all the possible show names
|
||||
"""
|
||||
|
||||
showNames = [show.name]
|
||||
showNames += [name for name in get_scene_exceptions(show.indexerid)]
|
||||
showNames = get_scene_exceptions(show.indexerid, season=season)
|
||||
if not showNames: # if we dont have any season specific exceptions fallback to generic exceptions
|
||||
season = -1
|
||||
showNames = get_scene_exceptions(show.indexerid, season=season)
|
||||
|
||||
if season in [-1, 1]:
|
||||
showNames.append(show.name)
|
||||
|
||||
newShowNames = []
|
||||
|
||||
country_list = countryList
|
||||
country_list.update(dict(zip(countryList.values(), countryList.keys())))
|
||||
country_list = common.countryList
|
||||
country_list.update(dict(zip(common.countryList.values(), common.countryList.keys())))
|
||||
|
||||
# if we have "Show Name Australia" or "Show Name (Australia)" this will add "Show Name (AU)" for
|
||||
# any countries defined in common.countryList
|
||||
# (and vice versa)
|
||||
for curName in set(showNames):
|
||||
if not curName:
|
||||
continue
|
||||
for curCountry in country_list:
|
||||
if curName.endswith(' ' + curCountry):
|
||||
newShowNames.append(curName.replace(' ' + curCountry, ' (' + country_list[curCountry] + ')'))
|
||||
elif curName.endswith(' (' + curCountry + ')'):
|
||||
newShowNames.append(curName.replace(' (' + curCountry + ')', ' (' + country_list[curCountry] + ')'))
|
||||
if not show.is_anime:
|
||||
for curName in set(showNames):
|
||||
if not curName:
|
||||
continue
|
||||
for curCountry in country_list:
|
||||
if curName.endswith(' ' + curCountry):
|
||||
newShowNames.append(curName.replace(' ' + curCountry, ' (' + country_list[curCountry] + ')'))
|
||||
elif curName.endswith(' (' + curCountry + ')'):
|
||||
newShowNames.append(curName.replace(' (' + curCountry + ')', ' (' + country_list[curCountry] + ')'))
|
||||
|
||||
showNames += newShowNames
|
||||
showNames += newShowNames
|
||||
|
||||
return showNames
|
||||
|
||||
|
@ -132,9 +132,9 @@ class ShowQueue(generic_queue.GenericQueue):
|
||||
return queueItemObj
|
||||
|
||||
def addShow(self, indexer, indexer_id, showDir, default_status=None, quality=None, flatten_folders=None,
|
||||
subtitles=None, lang="en"):
|
||||
subtitles=None, lang="en", anime=None):
|
||||
queueItemObj = QueueItemAdd(indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang,
|
||||
subtitles)
|
||||
subtitles, anime)
|
||||
|
||||
self.add_item(queueItemObj)
|
||||
|
||||
@ -189,7 +189,7 @@ class ShowQueueItem(generic_queue.QueueItem):
|
||||
|
||||
|
||||
class QueueItemAdd(ShowQueueItem):
|
||||
def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles):
|
||||
def __init__(self, indexer, indexer_id, showDir, default_status, quality, flatten_folders, lang, subtitles, anime):
|
||||
|
||||
self.indexer = indexer
|
||||
self.indexer_id = indexer_id
|
||||
@ -199,6 +199,7 @@ class QueueItemAdd(ShowQueueItem):
|
||||
self.flatten_folders = flatten_folders
|
||||
self.lang = lang
|
||||
self.subtitles = subtitles
|
||||
self.anime = anime
|
||||
|
||||
self.show = None
|
||||
|
||||
@ -283,6 +284,7 @@ class QueueItemAdd(ShowQueueItem):
|
||||
self.show.subtitles = self.subtitles if self.subtitles != None else sickbeard.SUBTITLES_DEFAULT
|
||||
self.show.quality = self.quality if self.quality else sickbeard.QUALITY_DEFAULT
|
||||
self.show.flatten_folders = self.flatten_folders if self.flatten_folders != None else sickbeard.FLATTEN_FOLDERS_DEFAULT
|
||||
#self.show.anime = self.anime if self.anime != None else sickbeard.ANIME_DEFAULT
|
||||
self.show.paused = False
|
||||
|
||||
# be smartish about this
|
||||
@ -292,7 +294,8 @@ class QueueItemAdd(ShowQueueItem):
|
||||
self.show.air_by_date = 0
|
||||
if self.show.classification and "sports" in self.show.classification.lower():
|
||||
self.show.sports = 1
|
||||
|
||||
if self.show.genre and "animation" in self.show.genre.lower():
|
||||
self.show.anime = 1
|
||||
|
||||
except sickbeard.indexer_exception, e:
|
||||
logger.log(
|
||||
|
@ -79,6 +79,7 @@ class TVShow(object):
|
||||
self.archive_firstmatch = 0
|
||||
self.lang = lang
|
||||
self.last_update_indexer = 1
|
||||
self.anime = 0
|
||||
|
||||
self.rls_ignore_words = ""
|
||||
self.rls_require_words = ""
|
||||
@ -94,6 +95,20 @@ class TVShow(object):
|
||||
|
||||
self.loadFromDB()
|
||||
|
||||
def _is_anime(self):
|
||||
if(self.anime > 0):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
is_anime = property(_is_anime)
|
||||
|
||||
def _is_sports(self):
|
||||
if(self.sports > 0):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
is_sports = property(_is_sports)
|
||||
|
||||
def _getLocation(self):
|
||||
# no dir check needed if missing show dirs are created during post-processing
|
||||
if sickbeard.CREATE_MISSING_SHOW_DIRS:
|
||||
@ -166,13 +181,30 @@ class TVShow(object):
|
||||
return ep_list
|
||||
|
||||
|
||||
def getEpisode(self, season, episode, file=None, noCreate=False):
|
||||
def getEpisode(self, season, episode, file=None, noCreate=False, absolute_number=None):
|
||||
|
||||
if not season in self.episodes:
|
||||
self.episodes[season] = {}
|
||||
|
||||
ep = None
|
||||
|
||||
# if we get an anime get the real season and episode
|
||||
if self.anime and absolute_number != None and season == None and episode == None:
|
||||
myDB = db.DBConnection()
|
||||
sql = "SELECT * FROM tv_episodes WHERE showid = ? and absolute_number = ? and season != 0"
|
||||
sqlResults = myDB.select(sql, [self.indexerid, absolute_number])
|
||||
|
||||
if len(sqlResults) == 1:
|
||||
episode = int(sqlResults[0]["episode"])
|
||||
season = int(sqlResults[0]["season"])
|
||||
logger.log("Found episode by absolute_number:"+str(absolute_number)+" which is "+str(season)+"x"+str(episode), logger.DEBUG)
|
||||
elif len(sqlResults) > 1:
|
||||
logger.log("Multiple entries for absolute number: "+str(absolute_number)+" in show: "+self.name+" found ", logger.ERROR)
|
||||
return None
|
||||
else:
|
||||
logger.log("No entries for absolute number: "+str(absolute_number)+" in show: "+self.name+" found.", logger.DEBUG)
|
||||
return None
|
||||
|
||||
if not episode in self.episodes[season] or self.episodes[season][episode] == None:
|
||||
if noCreate:
|
||||
return None
|
||||
@ -190,6 +222,7 @@ class TVShow(object):
|
||||
|
||||
epObj = self.episodes[season][episode]
|
||||
epObj.convertToSceneNumbering()
|
||||
|
||||
return epObj
|
||||
|
||||
def should_update(self, update_date=datetime.date.today()):
|
||||
@ -515,7 +548,7 @@ class TVShow(object):
|
||||
logger.log(str(self.indexerid) + u": Creating episode object from " + file, logger.DEBUG)
|
||||
|
||||
try:
|
||||
myParser = NameParser()
|
||||
myParser = NameParser(show=self, useIndexers=True)
|
||||
parse_result = myParser.parse(file)
|
||||
except InvalidNameException:
|
||||
logger.log(u"Unable to parse the filename " + file + " into a valid episode", logger.ERROR)
|
||||
@ -601,7 +634,7 @@ class TVShow(object):
|
||||
|
||||
# if they replace a file on me I'll make some attempt at re-checking the quality unless I know it's the same file
|
||||
if checkQualityAgain and not same_file:
|
||||
newQuality = Quality.nameQuality(file)
|
||||
newQuality = Quality.nameQuality(file, self.is_anime)
|
||||
logger.log(u"Since this file has been renamed, I checked " + file + " and found quality " +
|
||||
Quality.qualityStrings[newQuality], logger.DEBUG)
|
||||
if newQuality != Quality.UNKNOWN:
|
||||
@ -613,7 +646,7 @@ class TVShow(object):
|
||||
ARCHIVED, IGNORED]:
|
||||
|
||||
oldStatus, oldQuality = Quality.splitCompositeStatus(curEp.status)
|
||||
newQuality = Quality.nameQuality(file)
|
||||
newQuality = Quality.nameQuality(file, self.is_anime)
|
||||
if newQuality == Quality.UNKNOWN:
|
||||
newQuality = Quality.assumeQuality(file)
|
||||
|
||||
@ -720,6 +753,10 @@ class TVShow(object):
|
||||
if not self.lang:
|
||||
self.lang = sqlResults[0]["lang"]
|
||||
|
||||
self.anime = sqlResults[0]["anime"]
|
||||
if self.anime == None:
|
||||
self.anime = 0
|
||||
|
||||
self.last_update_indexer = sqlResults[0]["last_update_indexer"]
|
||||
|
||||
self.rls_ignore_words = sqlResults[0]["rls_ignore_words"]
|
||||
@ -1040,12 +1077,14 @@ class TVShow(object):
|
||||
"archive_firstmatch": self.archive_firstmatch,
|
||||
"startyear": self.startyear,
|
||||
"lang": self.lang,
|
||||
"anime": self.anime,
|
||||
"imdb_id": self.imdbid,
|
||||
"last_update_indexer": self.last_update_indexer,
|
||||
"rls_ignore_words": self.rls_ignore_words,
|
||||
"rls_require_words": self.rls_require_words
|
||||
}
|
||||
myDB.upsert("tv_shows", newValueDict, controlValueDict)
|
||||
helpers.update_anime_support()
|
||||
|
||||
if self.imdbid:
|
||||
controlValueDict = {"indexer_id": self.indexerid}
|
||||
@ -1071,6 +1110,7 @@ class TVShow(object):
|
||||
toReturn += "classification: " + self.classification + "\n"
|
||||
toReturn += "runtime: " + str(self.runtime) + "\n"
|
||||
toReturn += "quality: " + str(self.quality) + "\n"
|
||||
toReturn += "anime: " + str(self.is_anime) + "\n"
|
||||
return toReturn
|
||||
|
||||
|
||||
@ -1183,8 +1223,10 @@ class TVEpisode(object):
|
||||
self._name = ""
|
||||
self._season = season
|
||||
self._episode = episode
|
||||
self._absolute_number = 0
|
||||
self._scene_season = season
|
||||
self._scene_episode = episode
|
||||
self._scene_absolute_number = 0
|
||||
self._description = ""
|
||||
self._subtitles = list()
|
||||
self._subtitles_searchcount = 0
|
||||
@ -1218,8 +1260,10 @@ class TVEpisode(object):
|
||||
name = property(lambda self: self._name, dirty_setter("_name"))
|
||||
season = property(lambda self: self._season, dirty_setter("_season"))
|
||||
episode = property(lambda self: self._episode, dirty_setter("_episode"))
|
||||
absolute_number = property(lambda self: self._absolute_number, dirty_setter("_absolute_number"))
|
||||
scene_season = property(lambda self: self._scene_season, dirty_setter("_scene_season"))
|
||||
scene_episode = property(lambda self: self._scene_episode, dirty_setter("_scene_episode"))
|
||||
scene_absolute_number = property(lambda self: self._scene_absolute_number, dirty_setter("_scene_absolute_number"))
|
||||
description = property(lambda self: self._description, dirty_setter("_description"))
|
||||
subtitles = property(lambda self: self._subtitles, dirty_setter("_subtitles"))
|
||||
subtitles_searchcount = property(lambda self: self._subtitles_searchcount, dirty_setter("_subtitles_searchcount"))
|
||||
@ -1396,6 +1440,7 @@ class TVEpisode(object):
|
||||
|
||||
self.season = season
|
||||
self.episode = episode
|
||||
self.absolute_number = sqlResults[0]["absolute_number"]
|
||||
self.description = sqlResults[0]["description"]
|
||||
if not self.description:
|
||||
self.description = ""
|
||||
@ -1488,6 +1533,14 @@ class TVEpisode(object):
|
||||
self.deleteEpisode()
|
||||
return False
|
||||
|
||||
if myEp["absolute_number"] == None or myEp["absolute_number"] == "":
|
||||
logger.log(u"This episode ("+self.show.name+" - "+str(season)+"x"+str(episode)+") has no absolute number on " + sickbeard.indexerApi(
|
||||
self.indexer).name
|
||||
, logger.DEBUG)
|
||||
else:
|
||||
logger.log(str(self.show.indexerid) + ": The absolute_number for " + str(season) + "x" + str(episode)+" is : "+myEp["absolute_number"], logger.DEBUG)
|
||||
self.absolute_number = int(myEp["absolute_number"])
|
||||
|
||||
self.name = getattr(myEp, 'episodename', "")
|
||||
self.season = season
|
||||
self.episode = episode
|
||||
@ -1724,12 +1777,12 @@ class TVEpisode(object):
|
||||
|
||||
# use a custom update/insert method to get the data into the DB
|
||||
return [
|
||||
"INSERT OR REPLACE INTO tv_episodes (episode_id, indexerid, indexer, name, description, subtitles, subtitles_searchcount, subtitles_lastsearch, airdate, hasnfo, hastbn, status, location, file_size, release_name, is_proper, showid, season, episode) VALUES "
|
||||
"((SELECT episode_id FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
|
||||
"INSERT OR REPLACE INTO tv_episodes (episode_id, indexerid, indexer, name, description, subtitles, subtitles_searchcount, subtitles_lastsearch, airdate, hasnfo, hastbn, status, location, file_size, release_name, is_proper, showid, season, episode, absolute_number) VALUES "
|
||||
"((SELECT episode_id FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?),?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);",
|
||||
[self.show.indexerid, self.season, self.episode, self.indexerid, self.indexer, self.name, self.description,
|
||||
",".join([sub for sub in self.subtitles]), self.subtitles_searchcount, self.subtitles_lastsearch,
|
||||
self.airdate.toordinal(), self.hasnfo, self.hastbn, self.status, self.location, self.file_size,
|
||||
self.release_name, self.is_proper, self.show.indexerid, self.season, self.episode]]
|
||||
self.release_name, self.is_proper, self.show.indexerid, self.season, self.episode, self.absolute_number]]
|
||||
|
||||
def saveToDB(self, forceSave=False):
|
||||
"""
|
||||
@ -1763,7 +1816,9 @@ class TVEpisode(object):
|
||||
"location": self.location,
|
||||
"file_size": self.file_size,
|
||||
"release_name": self.release_name,
|
||||
"is_proper": self.is_proper}
|
||||
"is_proper": self.is_proper,
|
||||
"absolute_number": self.absolute_number
|
||||
}
|
||||
controlValueDict = {"showid": self.show.indexerid,
|
||||
"season": self.season,
|
||||
"episode": self.episode}
|
||||
@ -1784,6 +1839,7 @@ class TVEpisode(object):
|
||||
|
||||
Returns: A string representing the episode's name and season/ep numbers
|
||||
"""
|
||||
|
||||
return self._format_pattern('%SN - %Sx%0E - %EN')
|
||||
|
||||
def prettySceneName(self):
|
||||
@ -1898,6 +1954,7 @@ class TVEpisode(object):
|
||||
'%0XS': '%02d' % self.scene_season,
|
||||
'%XE': str(self.scene_episode),
|
||||
'%0XE': '%02d' % self.scene_episode,
|
||||
'%AN': '%03d' % self.absolute_number,
|
||||
'%RN': release_name(self.release_name),
|
||||
'%RG': release_group(self.release_name),
|
||||
'%AD': str(self.airdate).replace('-', ' '),
|
||||
@ -2183,13 +2240,15 @@ class TVEpisode(object):
|
||||
relEp.saveToDB()
|
||||
|
||||
def convertToSceneNumbering(self):
|
||||
(self.scene_season, self.scene_episode) = sickbeard.scene_numbering.get_scene_numbering(self.show.indexerid,
|
||||
(self.scene_season, self.scene_episode, self.scene_absolute_number) = sickbeard.scene_numbering.get_scene_numbering(self.show.indexerid,
|
||||
self.show.indexer,
|
||||
self.season,
|
||||
self.episode)
|
||||
self.episode,
|
||||
self.absolute_number)
|
||||
|
||||
def convertToIndexerNumbering(self):
|
||||
(self.season, self.episode) = sickbeard.scene_numbering.get_indexer_numbering(self.show.indexerid,
|
||||
(self.season, self.episode, self.absolute_number) = sickbeard.scene_numbering.get_indexer_numbering(self.show.indexerid,
|
||||
self.show.indexer,
|
||||
self.scene_season,
|
||||
self.scene_episode)
|
||||
self.scene_episode,
|
||||
self.scene_absolute_number)
|
||||
|
@ -145,7 +145,7 @@ class TVCache():
|
||||
parsed[2] = re.sub("/{2,}", "/", parsed[2]) # replace two or more / with one
|
||||
|
||||
if post_data:
|
||||
url = url + 'api?' + urllib.urlencode(post_data)
|
||||
url += urllib.urlencode(post_data)
|
||||
|
||||
f = fc.fetch(url)
|
||||
|
||||
@ -333,7 +333,7 @@ class TVCache():
|
||||
|
||||
# get quality of release
|
||||
if quality is None:
|
||||
quality = Quality.sceneQuality(name)
|
||||
quality = Quality.sceneQuality(name, parse_result.is_anime)
|
||||
|
||||
if not isinstance(name, unicode):
|
||||
name = unicode(name, 'utf-8')
|
||||
|
@ -59,7 +59,6 @@ from sickbeard.webapi import Api
|
||||
from sickbeard.scene_exceptions import get_scene_exceptions
|
||||
from sickbeard.scene_numbering import get_scene_numbering, set_scene_numbering, get_scene_numbering_for_show, \
|
||||
get_xem_numbering_for_show
|
||||
from sickbeard.providers.generic import TorrentProvider
|
||||
|
||||
from lib.dateutil import tz
|
||||
from lib.unrar2 import RarFile, RarInfo
|
||||
@ -77,7 +76,7 @@ except ImportError:
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
from sickbeard import browser
|
||||
|
||||
from lib import adba
|
||||
|
||||
def _handle_reverse_proxy():
|
||||
if sickbeard.HANDLE_REVERSE_PROXY:
|
||||
@ -565,6 +564,9 @@ class Manage:
|
||||
paused_all_same = True
|
||||
last_paused = None
|
||||
|
||||
anime_all_same = True
|
||||
last_anime = None
|
||||
|
||||
quality_all_same = True
|
||||
last_quality = None
|
||||
|
||||
@ -587,6 +589,13 @@ class Manage:
|
||||
else:
|
||||
last_paused = curShow.paused
|
||||
|
||||
if anime_all_same:
|
||||
# if we had a value already and this value is different then they're not all the same
|
||||
if last_anime not in (curShow.is_anime, None):
|
||||
anime_all_same = False
|
||||
else:
|
||||
last_anime = curShow.is_anime
|
||||
|
||||
if flatten_folders_all_same:
|
||||
if last_flatten_folders not in (None, curShow.flatten_folders):
|
||||
flatten_folders_all_same = False
|
||||
@ -607,6 +616,7 @@ class Manage:
|
||||
|
||||
t.showList = toEdit
|
||||
t.paused_value = last_paused if paused_all_same else None
|
||||
t.anime_value = last_anime if anime_all_same else None
|
||||
t.flatten_folders_value = last_flatten_folders if flatten_folders_all_same else None
|
||||
t.quality_value = last_quality if quality_all_same else None
|
||||
t.subtitles_value = last_subtitles if subtitles_all_same else None
|
||||
@ -615,7 +625,7 @@ class Manage:
|
||||
return _munge(t)
|
||||
|
||||
@cherrypy.expose
|
||||
def massEditSubmit(self, paused=None, flatten_folders=None, quality_preset=False, subtitles=None,
|
||||
def massEditSubmit(self, paused=None, anime=None, flatten_folders=None, quality_preset=False, subtitles=None,
|
||||
anyQualities=[], bestQualities=[], toEdit=None, *args, **kwargs):
|
||||
|
||||
dir_map = {}
|
||||
@ -649,6 +659,12 @@ class Manage:
|
||||
new_paused = True if paused == 'enable' else False
|
||||
new_paused = 'on' if new_paused else 'off'
|
||||
|
||||
if anime == 'keep':
|
||||
new_anime = showObj.is_anime
|
||||
else:
|
||||
new_anime = True if anime == 'enable' else False
|
||||
new_anime = 'on' if new_anime else 'off'
|
||||
|
||||
if flatten_folders == 'keep':
|
||||
new_flatten_folders = showObj.flatten_folders
|
||||
else:
|
||||
@ -668,7 +684,7 @@ class Manage:
|
||||
exceptions_list = []
|
||||
|
||||
curErrors += Home().editShow(curShow, new_show_dir, anyQualities, bestQualities, exceptions_list,
|
||||
new_flatten_folders, new_paused, subtitles=new_subtitles, directCall=True)
|
||||
new_flatten_folders, new_paused, subtitles=new_subtitles, anime=new_anime, directCall=True)
|
||||
|
||||
if curErrors:
|
||||
logger.log(u"Errors: " + str(curErrors), logger.ERROR)
|
||||
@ -942,6 +958,7 @@ ConfigMenu = [
|
||||
{'title': 'Subtitles Settings', 'path': 'config/subtitles/'},
|
||||
{'title': 'Post Processing', 'path': 'config/postProcessing/'},
|
||||
{'title': 'Notifications', 'path': 'config/notifications/'},
|
||||
{'title': 'Anime', 'path': 'config/anime/'},
|
||||
]
|
||||
|
||||
|
||||
@ -958,7 +975,7 @@ class ConfigGeneral:
|
||||
sickbeard.ROOT_DIRS = rootDirString
|
||||
|
||||
@cherrypy.expose
|
||||
def saveAddShowDefaults(self, defaultStatus, anyQualities, bestQualities, defaultFlattenFolders, subtitles=False):
|
||||
def saveAddShowDefaults(self, defaultStatus, anyQualities, bestQualities, defaultFlattenFolders, subtitles=False, anime=False):
|
||||
|
||||
if anyQualities:
|
||||
anyQualities = anyQualities.split(',')
|
||||
@ -978,6 +995,8 @@ class ConfigGeneral:
|
||||
sickbeard.FLATTEN_FOLDERS_DEFAULT = config.checkbox_to_value(defaultFlattenFolders)
|
||||
sickbeard.SUBTITLES_DEFAULT = config.checkbox_to_value(subtitles)
|
||||
|
||||
sickbeard.ANIME_DEFAULT = int(anime)
|
||||
|
||||
sickbeard.save_config()
|
||||
|
||||
@cherrypy.expose
|
||||
@ -1185,7 +1204,7 @@ class ConfigPostProcessing:
|
||||
wdtv_data=None, tivo_data=None, mede8er_data=None,
|
||||
keep_processed_dir=None, process_method=None, process_automatically=None,
|
||||
rename_episodes=None, airdate_episodes=None, unpack=None,
|
||||
move_associated_files=None, tv_download_dir=None, naming_custom_abd=None,
|
||||
move_associated_files=None, tv_download_dir=None, naming_custom_abd=None, naming_anime=None,
|
||||
naming_abd_pattern=None, naming_strip_year=None, use_failed_downloads=None,
|
||||
delete_failed=None, extra_scripts=None, skip_removed_files=None,
|
||||
naming_custom_sports=None, naming_sports_pattern=None, autopostprocesser_frequency=None):
|
||||
@ -1221,6 +1240,7 @@ class ConfigPostProcessing:
|
||||
sickbeard.NAMING_CUSTOM_ABD = config.checkbox_to_value(naming_custom_abd)
|
||||
sickbeard.NAMING_CUSTOM_SPORTS = config.checkbox_to_value(naming_custom_sports)
|
||||
sickbeard.NAMING_STRIP_YEAR = config.checkbox_to_value(naming_strip_year)
|
||||
sickbeard.NAMING_ANIME = config.checkbox_to_value(naming_anime)
|
||||
sickbeard.USE_FAILED_DOWNLOADS = config.checkbox_to_value(use_failed_downloads)
|
||||
sickbeard.DELETE_FAILED = config.checkbox_to_value(delete_failed)
|
||||
sickbeard.SKIP_REMOVED_FILES = config.checkbox_to_value(skip_removed_files)
|
||||
@ -1273,12 +1293,12 @@ class ConfigPostProcessing:
|
||||
redirect("/config/postProcessing/")
|
||||
|
||||
@cherrypy.expose
|
||||
def testNaming(self, pattern=None, multi=None, abd=False, sports=False):
|
||||
def testNaming(self, pattern=None, multi=None, abd=False, sports=False, anime=None):
|
||||
|
||||
if multi is not None:
|
||||
multi = int(multi)
|
||||
|
||||
result = naming.test_name(pattern, multi, abd, sports)
|
||||
result = naming.test_name(pattern, multi, abd, sports, anime)
|
||||
|
||||
result = ek.ek(os.path.join, result['dir'], result['name'])
|
||||
|
||||
@ -1966,6 +1986,52 @@ class ConfigSubtitles:
|
||||
|
||||
redirect("/config/subtitles/")
|
||||
|
||||
class ConfigAnime:
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
|
||||
t = PageTemplate(file="config_anime.tmpl")
|
||||
t.submenu = ConfigMenu
|
||||
return _munge(t)
|
||||
|
||||
@cherrypy.expose
|
||||
def saveAnime(self, use_anidb=None, anidb_username=None, anidb_password=None, anidb_use_mylist=None, split_home=None):
|
||||
|
||||
results = []
|
||||
|
||||
if use_anidb == "on":
|
||||
use_anidb = 1
|
||||
else:
|
||||
use_anidb = 0
|
||||
|
||||
if anidb_use_mylist == "on":
|
||||
anidb_use_mylist = 1
|
||||
else:
|
||||
anidb_use_mylist = 0
|
||||
|
||||
if split_home == "on":
|
||||
split_home = 1
|
||||
else:
|
||||
split_home = 0
|
||||
|
||||
sickbeard.USE_ANIDB = use_anidb
|
||||
sickbeard.ANIDB_USERNAME = anidb_username
|
||||
sickbeard.ANIDB_PASSWORD = anidb_password
|
||||
sickbeard.ANIDB_USE_MYLIST = anidb_use_mylist
|
||||
sickbeard.ANIME_SPLIT_HOME = split_home
|
||||
|
||||
sickbeard.save_config()
|
||||
|
||||
if len(results) > 0:
|
||||
for x in results:
|
||||
logger.log(x, logger.ERROR)
|
||||
ui.notifications.error('Error(s) Saving Configuration',
|
||||
'<br />\n'.join(results))
|
||||
else:
|
||||
ui.notifications.message('Configuration Saved', ek.ek(os.path.join, sickbeard.CONFIG_FILE) )
|
||||
|
||||
redirect("/config/anime/")
|
||||
|
||||
class Config:
|
||||
@cherrypy.expose
|
||||
@ -1987,6 +2053,7 @@ class Config:
|
||||
|
||||
subtitles = ConfigSubtitles()
|
||||
|
||||
anime = ConfigAnime()
|
||||
|
||||
def haveXBMC():
|
||||
return sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY
|
||||
@ -2250,7 +2317,7 @@ class NewHomeAddShows:
|
||||
@cherrypy.expose
|
||||
def addNewShow(self, whichSeries=None, indexerLang="en", rootDir=None, defaultStatus=None,
|
||||
anyQualities=None, bestQualities=None, flatten_folders=None, subtitles=None,
|
||||
fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None):
|
||||
fullShowPath=None, other_shows=None, skipShow=None, providedIndexer=None, anime=None):
|
||||
"""
|
||||
Receive tvdb id, dir, and other options and create a show from them. If extra show dirs are
|
||||
provided then it forwards back to newShow, if not it goes to /home.
|
||||
@ -2295,7 +2362,7 @@ class NewHomeAddShows:
|
||||
indexer_id = int(series_pieces[3])
|
||||
show_name = series_pieces[4]
|
||||
else:
|
||||
indexer = 1
|
||||
indexer = int(providedIndexer)
|
||||
indexer_id = int(whichSeries)
|
||||
show_name = os.path.basename(os.path.normpath(fullShowPath))
|
||||
|
||||
@ -2324,9 +2391,11 @@ class NewHomeAddShows:
|
||||
helpers.chmodAsParent(show_dir)
|
||||
|
||||
# prepare the inputs for passing along
|
||||
anime = config.checkbox_to_value(anime)
|
||||
flatten_folders = config.checkbox_to_value(flatten_folders)
|
||||
subtitles = config.checkbox_to_value(subtitles)
|
||||
|
||||
|
||||
if not anyQualities:
|
||||
anyQualities = []
|
||||
if not bestQualities:
|
||||
@ -2339,7 +2408,7 @@ class NewHomeAddShows:
|
||||
|
||||
# add the show
|
||||
sickbeard.showQueueScheduler.action.addShow(indexer, indexer_id, show_dir, int(defaultStatus), newQuality,
|
||||
flatten_folders, subtitles, indexerLang) # @UndefinedVariable
|
||||
flatten_folders, subtitles, indexerLang, anime) # @UndefinedVariable
|
||||
ui.notifications.message('Show added', 'Adding the specified show into ' + show_dir)
|
||||
|
||||
return finishAddShow()
|
||||
@ -2522,6 +2591,20 @@ class Home:
|
||||
def index(self):
|
||||
|
||||
t = PageTemplate(file="home.tmpl")
|
||||
|
||||
if sickbeard.ANIME_SPLIT_HOME:
|
||||
shows = []
|
||||
anime = []
|
||||
for show in sickbeard.showList:
|
||||
if show.is_anime:
|
||||
anime.append(show)
|
||||
else:
|
||||
shows.append(show)
|
||||
t.showlists = [["Shows",shows],
|
||||
["Anime",anime]]
|
||||
else:
|
||||
t.showlists = [["Shows",sickbeard.showList]]
|
||||
|
||||
t.submenu = HomeMenu()
|
||||
return _munge(t)
|
||||
|
||||
@ -2946,7 +3029,18 @@ class Home:
|
||||
x = x[4:]
|
||||
return x
|
||||
|
||||
t.sortedShowList = sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))
|
||||
if sickbeard.ANIME_SPLIT_HOME:
|
||||
shows = []
|
||||
anime = []
|
||||
for show in sickbeard.showList:
|
||||
if show.is_anime:
|
||||
anime.append(show)
|
||||
else:
|
||||
shows.append(show)
|
||||
t.sortedShowLists = [["Shows",sorted(shows, lambda x, y: cmp(titler(x.name), titler(y.name)))],
|
||||
["Anime",sorted(anime, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
|
||||
else:
|
||||
t.sortedShowLists = [["Shows",sorted(sickbeard.showList, lambda x, y: cmp(titler(x.name), titler(y.name)))]]
|
||||
|
||||
t.epCounts = epCounts
|
||||
t.epCats = epCats
|
||||
@ -2966,11 +3060,24 @@ class Home:
|
||||
(int(show), int(season), int(episode))).fetchone()
|
||||
return result['description'] if result else 'Episode not found.'
|
||||
|
||||
@cherrypy.expose
|
||||
def sceneExceptions(self, show):
|
||||
exceptionsList = sickbeard.scene_exceptions.get_all_scene_exceptions(show)
|
||||
if not exceptionsList:
|
||||
return "No scene exceptions"
|
||||
|
||||
out = []
|
||||
for season, names in iter(sorted(exceptionsList.iteritems())):
|
||||
if season == -1:
|
||||
season = "*"
|
||||
out.append("S" + str(season) + ": " + ", ".join(names))
|
||||
return "<br/>".join(out)
|
||||
|
||||
@cherrypy.expose
|
||||
def editShow(self, show=None, location=None, anyQualities=[], bestQualities=[], exceptions_list=[],
|
||||
flatten_folders=None, paused=None, directCall=False, air_by_date=None, sports=None, dvdorder=None,
|
||||
indexerLang=None, subtitles=None, archive_firstmatch=None, rls_ignore_words=None,
|
||||
rls_require_words=None):
|
||||
rls_require_words=None, anime=None):
|
||||
|
||||
if show is None:
|
||||
errString = "Invalid show ID: " + str(show)
|
||||
@ -2993,6 +3100,13 @@ class Home:
|
||||
if not location and not anyQualities and not bestQualities and not flatten_folders:
|
||||
t = PageTemplate(file="editShow.tmpl")
|
||||
t.submenu = HomeMenu()
|
||||
|
||||
if showObj.is_anime:
|
||||
t.groups = []
|
||||
if helpers.set_up_anidb_connection():
|
||||
anime = adba.Anime(sickbeard.ADBA_CONNECTION, name=showObj.name)
|
||||
t.groups = anime.get_groups()
|
||||
|
||||
with showObj.lock:
|
||||
t.show = showObj
|
||||
|
||||
@ -3008,6 +3122,7 @@ class Home:
|
||||
paused = config.checkbox_to_value(paused)
|
||||
air_by_date = config.checkbox_to_value(air_by_date)
|
||||
sports = config.checkbox_to_value(sports)
|
||||
anime = config.checkbox_to_value(anime)
|
||||
subtitles = config.checkbox_to_value(subtitles)
|
||||
|
||||
indexer_lang = indexerLang
|
||||
@ -3055,6 +3170,7 @@ class Home:
|
||||
if not directCall:
|
||||
showObj.air_by_date = air_by_date
|
||||
showObj.sports = sports
|
||||
showObj.anime = anime
|
||||
showObj.subtitles = subtitles
|
||||
showObj.lang = indexer_lang
|
||||
showObj.dvdorder = dvdorder
|
||||
|
@ -81,7 +81,7 @@ sickbeard.NAMING_MULTI_EP = 1
|
||||
|
||||
|
||||
sickbeard.PROVIDER_ORDER = ["sick_beard_index"]
|
||||
sickbeard.newznabProviderList = providers.getNewznabProviderList("'SickRage Index|http://lolo.sickbeard.com/|0|5030,5040,5060|0|eponly|0!!!NZBs.org|https://nzbs.org/||5030,5040,5060,5070,5090|0|eponly|0!!!Usenet-Crawler|https://www.usenet-crawler.com/||5030,5040,5060|0|eponly|0'")
|
||||
sickbeard.newznabProviderList = providers.getNewznabProviderList("'Sick Beard Index|http://lolo.sickbeard.com/|0|5030,5040,5060|0|eponly|0!!!NZBs.org|https://nzbs.org/||5030,5040,5060,5070,5090|0|eponly|0!!!Usenet-Crawler|https://www.usenet-crawler.com/||5030,5040,5060|0|eponly|0'")
|
||||
sickbeard.providerList = providers.makeProviderList()
|
||||
|
||||
sickbeard.PROG_DIR = os.path.abspath('..')
|
||||
|
Loading…
Reference in New Issue
Block a user