1
- using FlyleafLib . MediaFramework . MediaStream ;
2
- using Lingua ;
1
+ using System . Collections . Generic ;
3
2
using System . IO ;
3
+ using System . Linq ;
4
4
using System . Text ;
5
+ using FlyleafLib . MediaFramework . MediaStream ;
6
+ using Lingua ;
5
7
6
8
namespace FlyleafLib . Plugins ;
7
9
8
10
public class OpenSubtitles : PluginBase , IOpenSubtitles , ISearchLocalSubtitles
9
11
{
10
12
public new int Priority { get ; set ; } = 3000 ;
11
13
12
- private readonly Lazy < LanguageDetector > _languageDetector = new ( ( ) =>
14
+ private static readonly Lazy < LanguageDetector > LanguageDetector = new ( ( ) =>
13
15
{
14
16
LanguageDetector detector = LanguageDetectorBuilder
15
17
. FromAllLanguages ( )
@@ -18,9 +20,11 @@ public class OpenSubtitles : PluginBase, IOpenSubtitles, ISearchLocalSubtitles
18
20
return detector ;
19
21
} , true ) ;
20
22
23
+ private static readonly HashSet < string > ExtSet = new ( Utils . ExtensionsSubtitles , StringComparer . OrdinalIgnoreCase ) ;
24
+
21
25
public OpenSubtitlesResults Open ( string url )
22
26
{
23
- foreach ( var extStream in Selected . ExternalSubtitlesStreamsAll )
27
+ foreach ( var extStream in Selected . ExternalSubtitlesStreamsAll )
24
28
if ( extStream . Url == url )
25
29
return new OpenSubtitlesResults ( extStream ) ;
26
30
@@ -58,32 +62,53 @@ public void SearchLocalSubtitles()
58
62
{
59
63
try
60
64
{
61
- // Checks for text subtitles with the same file name and reads them
62
- // TODO: L: Search for subtitles with filenames like video file.XXX.srt
63
- // TODO: L: Allow reading from specific folders as well.
64
- foreach ( string ext in Utils . ExtensionsSubtitles )
65
+ string mediaDir = Path . GetDirectoryName ( Playlist . Url ) ;
66
+ string mediaName = Path . GetFileNameWithoutExtension ( Playlist . Url ) ;
67
+
68
+ OrderedDictionary < string , Language > result = new ( StringComparer . OrdinalIgnoreCase ) ;
69
+
70
+ CollectFromDirectory ( mediaDir , mediaName , result ) ;
71
+
72
+ // also search in subdirectories
73
+ string paths = Config . Subtitles . SearchLocalPaths ;
74
+ if ( ! string . IsNullOrWhiteSpace ( paths ) )
65
75
{
66
- string subPath = Path . ChangeExtension ( Playlist . Url , ext ) ;
67
- if ( File . Exists ( subPath ) )
76
+ foreach ( Range seg in paths . AsSpan ( ) . Split ( ';' ) )
68
77
{
69
- ExternalSubtitlesStream sub = new ( )
70
- {
71
- Url = subPath ,
72
- Title = Path . GetFileNameWithoutExtension ( subPath ) ,
73
- Downloaded = true ,
74
- IsBitmap = IsSubtitleBitmap ( subPath ) ,
75
- } ;
78
+ var path = paths . AsSpan ( seg ) . Trim ( ) ;
79
+ if ( path . IsEmpty ) continue ;
76
80
77
- if ( Config . Subtitles . LanguageAutoDetect && ! sub . IsBitmap )
81
+ string searchDir = ! Path . IsPathRooted ( path )
82
+ ? Path . Join ( mediaDir , path )
83
+ : path . ToString ( ) ;
84
+
85
+ if ( Directory . Exists ( searchDir ) )
78
86
{
79
- sub . Language = DetectLanguage ( subPath ) ;
80
- sub . LanguageDetected = true ;
87
+ CollectFromDirectory ( searchDir , mediaName , result ) ;
81
88
}
89
+ }
90
+ }
82
91
83
- Log . Debug ( $ "Adding [{ sub . Language . TopEnglishName } ] { subPath } ") ;
92
+ foreach ( var ( path , lang ) in result )
93
+ {
94
+ ExternalSubtitlesStream sub = new ( )
95
+ {
96
+ Url = path ,
97
+ Title = Path . GetFileNameWithoutExtension ( path ) ,
98
+ Downloaded = true ,
99
+ IsBitmap = IsSubtitleBitmap ( path ) ,
100
+ Language = lang
101
+ } ;
84
102
85
- AddExternalStream ( sub ) ;
103
+ if ( Config . Subtitles . LanguageAutoDetect && ! sub . IsBitmap && lang == Language . Unknown )
104
+ {
105
+ sub . Language = DetectLanguage ( path ) ;
106
+ sub . LanguageDetected = true ;
86
107
}
108
+
109
+ Log . Debug ( $ "Adding [{ sub . Language . TopEnglishName } ] { path } ") ;
110
+
111
+ AddExternalStream ( sub ) ;
87
112
}
88
113
}
89
114
catch ( Exception e )
@@ -92,8 +117,96 @@ public void SearchLocalSubtitles()
92
117
}
93
118
}
94
119
120
+ private static void CollectFromDirectory ( string searchDir , string filename , IDictionary < string , Language > result )
121
+ {
122
+ HashSet < int > added = null ;
123
+
124
+ // Get files starting with the same filename
125
+ List < string > fileList ;
126
+ try
127
+ {
128
+ fileList = Directory . GetFiles ( searchDir , $ "{ filename } .*", new EnumerationOptions { MatchCasing = MatchCasing . CaseInsensitive } )
129
+ . ToList ( ) ;
130
+ }
131
+ catch
132
+ {
133
+ return ;
134
+ }
135
+
136
+ if ( fileList . Count == 0 )
137
+ {
138
+ return ;
139
+ }
140
+
141
+ var files = fileList . Select ( f => new
142
+ {
143
+ FullPath = f ,
144
+ FileName = Path . GetFileName ( f )
145
+ } ) . ToList ( ) ;
146
+
147
+ // full match with top priority (video.srt, video.ass)
148
+ foreach ( string ext in ExtSet )
149
+ {
150
+ string expect = $ "{ filename } .{ ext } ";
151
+ int match = files . FindIndex ( x => string . Equals ( x . FileName , expect , StringComparison . OrdinalIgnoreCase ) ) ;
152
+ if ( match != - 1 )
153
+ {
154
+ result . TryAdd ( files [ match ] . FullPath , Language . Unknown ) ;
155
+ added ??= new HashSet < int > ( ) ;
156
+ added . Add ( match ) ;
157
+ }
158
+ }
159
+
160
+ // head match (video.*.srt, video.*.ass)
161
+ var extSetLookup = ExtSet . GetAlternateLookup < ReadOnlySpan < char > > ( ) ;
162
+ foreach ( var ( i , x ) in files . Index ( ) )
163
+ {
164
+ // skip full match
165
+ if ( added != null && added . Contains ( i ) )
166
+ {
167
+ continue ;
168
+ }
169
+
170
+ var span = x . FileName . AsSpan ( ) ;
171
+ var fileExt = Path . GetExtension ( span ) . TrimStart ( '.' ) ;
172
+
173
+ // Check if the file is a subtitle file by its extension
174
+ if ( extSetLookup . Contains ( fileExt ) )
175
+ {
176
+ var name = Path . GetFileNameWithoutExtension ( span ) ;
177
+
178
+ if ( ! name . StartsWith ( filename + '.' , StringComparison . OrdinalIgnoreCase ) )
179
+ {
180
+ continue ;
181
+ }
182
+
183
+ Language lang = Language . Unknown ;
184
+
185
+ var extraPart = name . Slice ( filename . Length + 1 ) ; // Skip file name and dot
186
+ if ( extraPart . Length > 0 )
187
+ {
188
+ foreach ( var codeSeg in extraPart . Split ( '.' ) )
189
+ {
190
+ var code = extraPart [ codeSeg ] ;
191
+ if ( code . Length > 0 )
192
+ {
193
+ Language parsed = Language . Get ( code . ToString ( ) ) ;
194
+ if ( ! string . IsNullOrEmpty ( parsed . IdSubLanguage ) && parsed . IdSubLanguage != "und" )
195
+ {
196
+ lang = parsed ;
197
+ break ;
198
+ }
199
+ }
200
+ }
201
+ }
202
+
203
+ result . TryAdd ( x . FullPath , lang ) ;
204
+ }
205
+ }
206
+ }
207
+
95
208
// TODO: L: To check the contents of a file by determining the bitmap.
96
- private bool IsSubtitleBitmap ( string path )
209
+ private static bool IsSubtitleBitmap ( string path )
97
210
{
98
211
try
99
212
{
@@ -108,7 +221,7 @@ private bool IsSubtitleBitmap(string path)
108
221
}
109
222
110
223
// TODO: L: Would it be better to check with SubtitlesManager for network subtitles?
111
- private Language DetectLanguage ( string path )
224
+ private static Language DetectLanguage ( string path )
112
225
{
113
226
if ( ! File . Exists ( path ) )
114
227
{
@@ -139,7 +252,7 @@ private Language DetectLanguage(string path)
139
252
140
253
string content = encoding . GetString ( data ) ;
141
254
142
- var detectedLanguage = _languageDetector . Value . DetectLanguageOf ( content ) ;
255
+ var detectedLanguage = LanguageDetector . Value . DetectLanguageOf ( content ) ;
143
256
144
257
if ( detectedLanguage == Lingua . Language . Unknown )
145
258
{
0 commit comments