subscene.py 6.48 KB
Newer Older
echel0n's avatar
echel0n committed
1
# -*- coding: utf-8 -*-
echel0n's avatar
echel0n committed
2
3
4
5
# Author: echel0n <[email protected]>
#
# URL: https://sickrage.ca
#
echel0n's avatar
echel0n committed
6
# This file is part of SiCKRAGE.
echel0n's avatar
echel0n committed
7
#
echel0n's avatar
echel0n committed
8
# SiCKRAGE is free software: you can redistribute it and/or modify
echel0n's avatar
echel0n committed
9
10
11
12
# 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.
#
echel0n's avatar
echel0n committed
13
# SiCKRAGE is distributed in the hope that it will be useful,
echel0n's avatar
echel0n committed
14
15
16
17
18
# 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
echel0n's avatar
echel0n committed
19
# along with SiCKRAGE. If not, see <http://www.gnu.org/licenses/>.
echel0n's avatar
echel0n committed
20

echel0n's avatar
echel0n committed
21
22
23
24
25

import bisect
import io
import logging
import os
echel0n's avatar
echel0n committed
26
import re
echel0n's avatar
echel0n committed
27
28
import zipfile

echel0n's avatar
echel0n committed
29
from babelfish import Language, language_converters
echel0n's avatar
echel0n committed
30
31
32
33
from guessit import guessit
from requests import Session
from subliminal import Provider
from subliminal.exceptions import ProviderError
34
from subliminal.matches import guess_matches
echel0n's avatar
echel0n committed
35
from subliminal.providers import ParserBeautifulSoup
36
from subliminal.subtitle import Subtitle, fix_line_ending
echel0n's avatar
echel0n committed
37
38
39
40
41
from subliminal.utils import sanitize
from subliminal.video import Episode, Movie

logger = logging.getLogger(__name__)

42
language_converters.register('subscene = sickrage.subtitles.converters.subscene:SubsceneConverter')
echel0n's avatar
echel0n committed
43

echel0n's avatar
echel0n committed
44

echel0n's avatar
echel0n committed
45
46
47
class SubsceneSubtitle(Subtitle):
    """Subscene Subtitle."""
    provider_name = 'subscene'
echel0n's avatar
echel0n committed
48

echel0n's avatar
echel0n committed
49
50
    def __init__(self, language, hearing_impaired, series, season, episode, title, sub_id, releases):
        super(SubsceneSubtitle, self).__init__(language, hearing_impaired)
echel0n's avatar
echel0n committed
51
52
53
54
        self.series = series
        self.season = season
        self.episode = episode
        self.title = title
echel0n's avatar
echel0n committed
55
        self.sub_id = sub_id
echel0n's avatar
echel0n committed
56
57
58
59
60
        self.downloaded = 0
        self.releases = releases

    @property
    def id(self):
echel0n's avatar
echel0n committed
61
        return str(self.sub_id)
echel0n's avatar
echel0n committed
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

    def get_matches(self, video):
        matches = set()

        # episode
        if isinstance(video, Episode):
            # series
            if video.series and sanitize(self.series) == sanitize(video.series):
                matches.add('series')
            # season
            if video.season and self.season == video.season:
                matches.add('season')
            # episode
            if video.episode and self.episode == video.episode:
                matches.add('episode')
            # guess
            for release in self.releases:
                matches |= guess_matches(video, guessit(release, {'type': 'episode'}))
        # movie
        elif isinstance(video, Movie):
            # guess
            for release in self.releases:
                matches |= guess_matches(video, guessit(release, {'type': 'movie'}))

        # title
        if video.title and sanitize(self.title) == sanitize(video.title):
            matches.add('title')

        return matches


echel0n's avatar
echel0n committed
93
94
95
96
class SubsceneProvider(Provider):
    """Subscene Provider."""
    languages = {Language.fromsubscene(l) for l in language_converters['subscene'].codes}
    server_url = 'https://subscene.com'
echel0n's avatar
echel0n committed
97
98
99
100
101
102
103
104
105
106

    def __init__(self):
        self.session = None

    def initialize(self):
        self.session = Session()

    def terminate(self):
        self.session.close()

echel0n's avatar
echel0n committed
107
108
109
110
111
112
    def query(self, title, season=None, episode=None):
        url = '{}/subtitles/release'.format(self.server_url)
        params = {
            'q': '{0} S{1:02}E{2:02}'.format(title, season, episode),
            'r': 'true'
        }
echel0n's avatar
echel0n committed
113
114
115

        # get the list of subtitles
        logger.debug('Getting the list of subtitles')
echel0n's avatar
echel0n committed
116
117

        r = self.session.get(url, params=params, timeout=30)
echel0n's avatar
echel0n committed
118
119
        r.raise_for_status()

echel0n's avatar
echel0n committed
120
        soup = ParserBeautifulSoup(r.content, ['html5lib', 'html.parser'])
echel0n's avatar
echel0n committed
121
122
123

        # loop over results
        subtitles = {}
echel0n's avatar
echel0n committed
124
125
126
127
128
129
130
131
132
133
134
135

        subtitle_table = soup.find('table')
        subtitle_rows = subtitle_table('tr') if subtitle_table else []

        # Continue only if one subtitle is found
        if len(subtitle_rows) < 2:
            return subtitles.values()

        for row in subtitle_rows[1:]:
            cells = row('td')

            language = Language.fromsubscene(cells[0].find_all('span')[0].get_text(strip=True))
136
            hearing_impaired = (False, True)[list(cells[2].attrs.values())[0] == 41]
echel0n's avatar
echel0n committed
137
138
139
140
141
142
143
144
145
146
147
            page_link = cells[0].find('a')['href']
            release = cells[0].find_all('span')[1].get_text(strip=True)

            # guess from name
            guess = guessit(release, {'type': 'episode'})
            if guess.get('season') != season and guess.get('episode') != episode:
                continue

            r = self.session.get(self.server_url + page_link, timeout=30)
            r.raise_for_status()
            soup2 = ParserBeautifulSoup(r.content, ['html5lib', 'html.parser'])
148
149
150
151
152

            try:
                sub_id = re.search(r'\?mac=(.*)', soup2.find('a', id='downloadButton')['href']).group(1)
            except AttributeError:
                continue
echel0n's avatar
echel0n committed
153
154

            # add the release and increment downloaded count if we already have the subtitle
echel0n's avatar
echel0n committed
155
156
157
158
            if sub_id in subtitles:
                logger.debug('Found additional release %r for subtitle %d', release, sub_id)
                bisect.insort_left(subtitles[sub_id].releases, release)  # deterministic order
                subtitles[sub_id].downloaded += 1
echel0n's avatar
echel0n committed
159
160
161
                continue

            # otherwise create it
echel0n's avatar
echel0n committed
162
163
            subtitle = SubsceneSubtitle(language, hearing_impaired, title, season, episode, title, sub_id, [release])

echel0n's avatar
echel0n committed
164
            logger.debug('Found subtitle %r', subtitle)
echel0n's avatar
echel0n committed
165
            subtitles[sub_id] = subtitle
echel0n's avatar
echel0n committed
166
167
168
169

        return subtitles.values()

    def list_subtitles(self, video, languages):
echel0n's avatar
echel0n committed
170
171
        return [s for s in self.query(video.series, video.season, video.episode)
                if s is not None and s.language in languages]
echel0n's avatar
echel0n committed
172

echel0n's avatar
echel0n committed
173
174
175
    def download_subtitle(self, subtitle):
        # download the subtitle
        logger.info('Downloading subtitle %r', subtitle.sub_id)
echel0n's avatar
echel0n committed
176

echel0n's avatar
echel0n committed
177
178
179
        params = {
            'mac': subtitle.sub_id
        }
echel0n's avatar
echel0n committed
180

echel0n's avatar
echel0n committed
181
        r = self.session.get(self.server_url + '/subtitle/download', params=params, timeout=30)
echel0n's avatar
echel0n committed
182
183
184
185
186
187
188
189
190
191
        r.raise_for_status()

        # open the zip
        with zipfile.ZipFile(io.BytesIO(r.content)) as zf:
            # remove some filenames from the namelist
            namelist = [n for n in zf.namelist() if os.path.splitext(n)[1] in ['.srt', '.sub']]
            if len(namelist) > 1:
                raise ProviderError('More than one file to unzip')

            subtitle.content = fix_line_ending(zf.read(namelist[0]))