1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
|
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import logging
import os
import io
import rarfile
import zipfile
from guessit import guessit
from requests import Session
from subliminal import Episode
from subliminal.exceptions import AuthenticationError, ConfigurationError
from subliminal_patch.subtitle import Subtitle, guess_matches
from subliminal.subtitle import fix_line_ending, SUBTITLE_EXTENSIONS
from subliminal_patch.providers import Provider
from subzero.language import Language
logger = logging.getLogger(__name__)
server_url = 'https://api.betaseries.com/'
class BetaSeriesSubtitle(Subtitle):
provider_name = 'betaseries'
def __init__(self, subtitle_id, language, video_name, url, matches, source, video_release_group):
super(BetaSeriesSubtitle, self).__init__(language, page_link=url)
self.subtitle_id = subtitle_id
self.video_name = video_name
self.download_url = url
self.matches = matches
self.source = source
self.video_release_group = video_release_group
self.release_info = video_name
@property
def id(self):
return self.subtitle_id
@property
def download_link(self):
return self.download_url
def get_matches(self, video):
matches = self.matches
if isinstance(video, Episode):
matches |= guess_matches(video, guessit(
self.video_name, {'type': 'episode'}), partial=True)
return matches
class BetaSeriesProvider(Provider):
"""BetaSeries Provider"""
languages = {Language(l) for l in ['fra', 'eng']}
video_types = (Episode,)
def __init__(self, token=None):
if not token:
raise ConfigurationError('Token must be specified')
self.token = token
self.video = None
def initialize(self):
self.session = Session()
self.session.headers = {
'User-Agent': os.environ.get("SZ_USER_AGENT", "Sub-Zero/2")}
def terminate(self):
self.session.close()
def query(self, languages, video):
# query the server
self.video = video
matches = set()
if video.tvdb_id:
params = {'key': self.token,
'thetvdb_id': video.tvdb_id,
'v': 3.0,
'subtitles': 1}
logger.debug('Searching subtitles %r', params)
res = self.session.get(
server_url + 'episodes/display', params=params, timeout=10)
res.raise_for_status()
result = res.json()
matches.add('tvdb_id')
elif video.series_tvdb_id:
params = {'key': self.token,
'thetvdb_id': video.series_tvdb_id,
'season': video.season,
'episode': video.episode,
'subtitles': 1,
'v': 3.0}
logger.debug('Searching subtitles %r', params)
res = self.session.get(
server_url + 'shows/episodes', params=params, timeout=10)
if res.status_code == 400:
raise AuthenticationError("Invalid token provided")
res.raise_for_status()
result = res.json()
matches.add('series_tvdb_id')
else:
logger.debug(
'The show has no tvdb_id and series_tvdb_id: the search can\'t be done')
return []
if result['errors'] != []:
logger.debug('Status error: %r', result['errors'])
return []
# parse the subtitles
subtitles = []
if 'episode' in result and 'subtitles' in result['episode']:
subs = result['episode']['subtitles']
elif 'episodes' in result and len(result['episodes'] and 'subtitles' in result['episodes'][0]):
subs = result['episodes'][0]['subtitles']
else:
return []
for sub in subs:
language = _translateLanguageCodeToLanguage(sub['language'])
if language in languages:
# Filter seriessub source because it shut down so the links are all 404
if str(sub['source']) != 'seriessub':
subtitles.append(BetaSeriesSubtitle(
sub['id'], language, sub['file'], sub['url'], matches, str(sub['source']),
self.video.release_group))
return subtitles
def list_subtitles(self, video, languages):
return self.query(languages, video)
def download_subtitle(self, subtitle):
logger.info('Downloading subtitle %r', subtitle)
r = self.session.get(subtitle.download_link, timeout=10)
if r.status_code == 404:
logger.error('Error 404 downloading %r', subtitle)
return
else:
r.raise_for_status()
archive = _get_archive(r.content)
if archive:
subtitle_names = _get_subtitle_names_from_archive(archive)
subtitle_to_download = _choose_subtitle_with_release_group(subtitle_names, subtitle.video_release_group)
logger.debug('Subtitle to download: ' + subtitle_to_download)
subtitle_content = archive.read(subtitle_to_download)
else:
subtitle_content = r.content
if subtitle_content:
subtitle.content = fix_line_ending(subtitle_content)
else:
logger.error('Could not extract subtitle from %r', archive)
def _get_archive(content):
# open the archive
archive_stream = io.BytesIO(content)
archive = None
if rarfile.is_rarfile(archive_stream):
logger.debug('Identified rar archive')
archive = rarfile.RarFile(archive_stream)
elif zipfile.is_zipfile(archive_stream):
logger.debug('Identified zip archive')
archive = zipfile.ZipFile(archive_stream)
return archive
def _get_subtitle_names_from_archive(archive):
subtitlesToConsider = []
for name in archive.namelist():
# discard hidden files
if os.path.split(name)[-1].startswith('.'):
continue
# discard non-subtitle files
if not name.lower().endswith(SUBTITLE_EXTENSIONS):
continue
subtitlesToConsider.append(name)
if len(subtitlesToConsider)>0:
logger.debug('Subtitles in archive: ' + ' '.join(subtitlesToConsider))
return subtitlesToConsider
else:
return None
def _translateLanguageCodeToLanguage(languageCode):
if languageCode.lower() == 'vo':
return Language.fromalpha2('en')
elif languageCode.lower() == 'vf':
return Language.fromalpha2('fr')
def _choose_subtitle_with_release_group(subtitle_names, release_group):
if release_group:
for subtitle in subtitle_names:
if release_group in subtitle:
return subtitle
return subtitle_names[0]
|