]> git.sesse.net Git - vlc/blob - src/input/subtitles.c
* Fix the sub autodetect directory string
[vlc] / src / input / subtitles.c
1 /*****************************************************************************
2  * subtitles.c
3  *****************************************************************************
4  * Copyright (C) 2003-2004 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Derk-Jan Hartman <hartman at videolan.org>
8  * This is adapted code from the GPL'ed MPlayer (http://mplayerhq.hu)
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /**
26  *  \file
27  *  This file contains functions to dectect subtitle files.
28  */
29
30 #include <stdlib.h>
31 #include <vlc/vlc.h>
32 #include <vlc/input.h>
33 #include "charset.h"
34
35 #ifdef HAVE_DIRENT_H
36 #   include <dirent.h>
37 #endif
38
39 #ifdef HAVE_LIMITS_H  
40 #   include <limits.h>  
41 #endif
42
43 #include <ctype.h>
44
45 /**
46  * What's between a directory and a filename?
47  */
48 #if defined( WIN32 )
49     #define DIRECTORY_SEPARATOR '\\'
50 #else
51     #define DIRECTORY_SEPARATOR '/'
52 #endif
53
54 /**
55  * We are not going to autodetect more subtitle files than this.
56  */
57 #define MAX_SUBTITLE_FILES 128
58
59
60 /**
61  * The possible extentions for subtitle files we support
62  */
63 static const char * sub_exts[] = {  "utf", "utf8", "utf-8", "sub", "srt", "smi", "txt", "ssa", "idx", NULL};
64 /* extensions from unsupported types */
65 /* rt, aqt, jss, js, ass */
66
67 static void strcpy_trim( char *d, char *s )
68 {
69     /* skip leading whitespace */
70     while( *s && !isalnum(*s) )
71     {
72         s++;
73     }
74     for(;;)
75     {
76         /* copy word */
77         while( *s && isalnum(*s) )
78         {
79             *d = tolower(*s);
80             s++; d++;
81         }
82         if( *s == 0 ) break;
83         /* trim excess whitespace */
84         while( *s && !isalnum(*s) )
85         {
86             s++;
87         }
88         if( *s == 0 ) break;
89         *d++ = ' ';
90     }
91     *d = 0;
92 }
93
94 static void strcpy_strip_ext( char *d, char *s )
95 {
96     char *tmp = strrchr(s, '.');
97     if( !tmp )
98     {
99         strcpy(d, s);
100         return;
101     }
102     else
103     {
104         strncpy(d, s, tmp - s);
105         d[tmp - s] = 0;
106     }
107     while( *d )
108     {
109         *d = tolower(*d);
110         d++;
111     }
112 }
113
114 static void strcpy_get_ext( char *d, char *s )
115 {
116     char *tmp = strrchr(s, '.');
117     if( !tmp )
118     {
119         strcpy(d, "");
120         return;
121     } else strcpy( d, tmp + 1 );
122 }
123
124 static int whiteonly( char *s )
125 {
126   while ( *s )
127   {
128         if( isalnum( *s ) ) return 0;
129         s++;
130   }
131   return 1;
132 }
133
134 typedef struct _subfn
135 {
136     int priority;
137     char *psz_fname;
138     char *psz_ext;
139 } subfn;
140
141 static int compare_sub_priority( const void *a, const void *b )
142 {
143     if (((subfn*)a)->priority > ((subfn*)b)->priority)
144     {
145         return -1;
146     }
147
148     if (((subfn*)a)->priority < ((subfn*)b)->priority)
149     {
150         return 1;
151     }
152
153 #ifndef UNDER_CE
154     return strcoll(((subfn*)a)->psz_fname, ((subfn*)b)->psz_fname);
155 #else
156     return strcmp(((subfn*)a)->psz_fname, ((subfn*)b)->psz_fname);
157 #endif
158 }
159
160 /* Utility function for scandir */  
161 static int Filter( const struct dirent *p_dir_content )
162 {
163     int i;
164     char *tmp = NULL;
165
166     if( p_dir_content == NULL || p_dir_content->d_name == NULL ) return VLC_FALSE;
167     /* does it end with a subtitle extension? */
168     tmp = strrchr( p_dir_content->d_name, '.');
169     if( !tmp )
170     {
171         return VLC_FALSE;
172     }
173     else
174     {
175         for( i = 0; sub_exts[i]; i++ )
176         {
177             if( strcmp( sub_exts[i], tmp+1 ) == 0 )
178             {
179                 return VLC_TRUE;
180             }
181         }
182     }
183     return VLC_FALSE;
184 }
185
186
187 /**
188  * Convert a list of paths separated by ',' to a char**
189  */
190 static char **paths_to_list( char *psz_dir, char *psz_path )
191 {
192     unsigned int i, k, i_nb_subdirs;
193     char **subdirs; /* list of subdirectories to look in */
194
195     if( !psz_dir ) return NULL;
196     if( !psz_path ) return NULL;
197
198     i_nb_subdirs = 1;
199     for( k = 0; k < strlen( psz_path ); k++ )
200     {
201         if( psz_path[k] == ',' )
202         {
203             i_nb_subdirs++;
204         }
205     }
206
207     if( i_nb_subdirs > 0 )
208     {
209         char *psz_parser = NULL, *psz_temp = NULL;
210
211         subdirs = (char**)malloc( sizeof(char*) * ( i_nb_subdirs + 1 ) );
212         memset( subdirs, 0, sizeof(char*) * ( i_nb_subdirs + 1 ) );
213         i = 0;
214         psz_parser = psz_path;
215         while( psz_parser && *psz_parser )
216         {
217             char *psz_subdir;
218             psz_subdir = psz_parser;
219             psz_parser = strchr( psz_subdir, ',' );
220             if( psz_parser )
221             {
222                 *psz_parser = '\0';
223                 psz_parser++;
224                 while( *psz_parser == ' ' )
225                 {
226                     psz_parser++;
227                 }
228             }
229             if( strlen( psz_subdir ) > 0 )
230             {
231                 psz_temp = (char *)malloc( strlen(psz_dir)
232                                            + strlen(psz_subdir) + 2 );
233                 if( psz_temp )
234                 {
235                     sprintf( psz_temp, "%s%s%c", 
236                              psz_subdir[0] == '.' ? psz_dir : "",
237                              psz_subdir,
238                              psz_subdir[strlen(psz_subdir) - 1] ==
239                               DIRECTORY_SEPARATOR ? '\0' : DIRECTORY_SEPARATOR );
240                     subdirs[i] = psz_temp;
241                     i++;
242                 }
243             }
244         }
245         subdirs[i] = NULL;
246     }
247     else
248     {
249         subdirs = NULL;
250     }
251     return subdirs;
252 }
253
254
255 /**
256  * Detect subtitle files.
257  *
258  * When called this function will split up the psz_name string into a
259  * directory, filename and extension. It then opens the directory
260  * in which the file resides and tries to find possible matches of
261  * subtitles files.
262  *
263  * \ingroup Demux
264  * \param p_this the calling \ref input_thread_t
265  * \param psz_path a list of subdirectories (separated by a ',') to look in.
266  * \param psz_name the complete filename to base the search on.
267  * \return a NULL terminated array of filenames with detected possible subtitles.
268  * The array contains max MAX_SUBTITLE_FILES items and you need to free it after use.
269  */
270 char **subtitles_Detect( input_thread_t *p_this, char *psz_path,
271                          char *psz_name )
272 {
273     vlc_value_t fuzzy;
274     int j, i_result2, i_dir_content, i_sub_count = 0, i_fname_len = 0;
275     char *f_dir = NULL, *f_fname = NULL, *f_fname_noext = NULL, *f_fname_trim = NULL;
276     char *tmp = NULL;
277
278     char tmp_fname_noext[PATH_MAX];
279     char tmp_fname_trim[PATH_MAX];
280     char tmp_fname_ext[PATH_MAX];
281
282     struct dirent **pp_dir_content;
283     char **tmp_subdirs, **subdirs; /* list of subdirectories to look in */
284
285     subfn *result = NULL; /* unsorted results */
286     char **result2; /* sorted results */
287
288     char *psz_fname_original = strdup( psz_name );
289     char *psz_fname = psz_fname_original;
290
291     if( !strncmp( psz_fname, "file://", 7 ) )
292     {
293         psz_fname += 7;
294     }
295
296     /* extract filename & dirname from psz_fname */
297     tmp = strrchr( psz_fname, DIRECTORY_SEPARATOR );
298     if( tmp )
299     {
300         int dirlen = 0;
301
302         f_fname = malloc( strlen(tmp) );
303         if( f_fname )
304             strcpy( f_fname, tmp+1 ); // we skip the seperator, so it will still fit in the allocated space
305         dirlen = strlen(psz_fname) - strlen(tmp) + 1; // add the seperator
306         f_dir = malloc( dirlen + 1 );
307         if( f_dir )
308         {
309             strncpy( f_dir, psz_fname, dirlen );
310             f_dir[dirlen] = 0;
311         }
312     }
313     else
314     {
315         f_fname = strdup( psz_fname );
316     }
317
318     i_fname_len = strlen( f_fname );
319     f_fname_noext = malloc(i_fname_len + 1);
320     f_fname_trim = malloc(i_fname_len + 1 );
321
322     strcpy_strip_ext( f_fname_noext, f_fname );
323     strcpy_trim( f_fname_trim, f_fname_noext );
324
325     result = (subfn*)malloc( sizeof(subfn) * MAX_SUBTITLE_FILES );
326     if( result )
327         memset( result, 0, sizeof(subfn) * MAX_SUBTITLE_FILES );
328
329     var_Get( p_this, "sub-autodetect-fuzzy", &fuzzy );
330
331     tmp_subdirs = paths_to_list( f_dir, psz_path );
332     subdirs = tmp_subdirs;
333
334     for( j = -1; (j == -1) || ( (j >= 0) && (subdirs != NULL) && (*subdirs != NULL) );
335          j++)
336     {
337         pp_dir_content = NULL;
338         i_dir_content = 0;
339
340         /* parse psz_src dir */  
341         if( ( i_dir_content = scandir( j < 0 ? f_dir : *subdirs, &pp_dir_content, Filter,
342                                 NULL ) ) != -1 )
343         {
344             int a;
345
346             msg_Dbg( p_this, "looking for a subtitle file in %s", j < 0 ? f_dir : *subdirs );
347             for( a = 0; a < i_dir_content; a++ )
348             {
349                 int i_prio = 0;
350                 struct dirent *p_dir_content = pp_dir_content[a];
351                 char *psz_inUTF8 = FromLocale( p_dir_content->d_name );
352                 char *p_fixed_name = vlc_fix_readdir_charset( p_this, psz_inUTF8 );
353
354                 LocaleFree( psz_inUTF8 );
355
356                 /* retrieve various parts of the filename */
357                 strcpy_strip_ext( tmp_fname_noext, p_fixed_name );
358                 strcpy_get_ext( tmp_fname_ext, p_fixed_name );
359                 strcpy_trim( tmp_fname_trim, tmp_fname_noext );
360
361                 if( !i_prio && !strcmp( tmp_fname_trim, f_fname_trim ) )
362                 {
363                     /* matches the movie name exactly */
364                     i_prio = 4;
365                 }
366                 if( !i_prio &&
367                     ( tmp = strstr( tmp_fname_trim, f_fname_trim ) ) )
368                 {
369                     /* contains the movie name */
370                     tmp += strlen( f_fname_trim );
371                     if( whiteonly( tmp ) )
372                     {
373                         /* chars in front of the movie name */
374                         i_prio = 2;
375                     }
376                     else
377                     {
378                         /* chars after (and possibly in front of)
379                          * the movie name */
380                         i_prio = 3;
381                     }
382                 }
383                 if( !i_prio )
384                 {
385                     /* doesn't contain the movie name */
386                     if( j == 0 ) i_prio = 1;
387                 }
388                 if( i_prio >= fuzzy.i_int )
389                 {
390                     FILE *f;
391                     char *tmpresult;
392
393                     asprintf( &tmpresult, "%s%s", j < 0 ? f_dir : *subdirs, p_fixed_name );
394                     msg_Dbg( p_this, "autodetected subtitle: %s with priority %d", p_fixed_name, i_prio );
395                     if( ( f = fopen( tmpresult, "rt" ) ) )
396                     {
397                         fclose( f );
398                         result[i_sub_count].priority = i_prio;
399                         result[i_sub_count].psz_fname = tmpresult;
400                         result[i_sub_count].psz_ext = strdup(tmp_fname_ext);
401                         i_sub_count++;
402                     } else free( tmpresult );
403                 }
404                 if( i_sub_count >= MAX_SUBTITLE_FILES ) break;
405                 free( p_fixed_name );
406             }
407         }
408         if( j >= 0 ) free( *subdirs++ );
409     }
410
411     if( tmp_subdirs )   free( tmp_subdirs );
412     if( f_fname_trim )  free( f_fname_trim );
413     if( f_fname_noext ) free( f_fname_noext );
414     if( f_fname ) free( f_fname );
415     if( f_dir )   free( f_dir );
416
417     qsort( result, i_sub_count, sizeof( subfn ), compare_sub_priority );
418
419     result2 = (char**)malloc( sizeof(char*) * ( i_sub_count + 1 ) );
420     if( result2 )
421         memset( result2, 0, sizeof(char*) * ( i_sub_count + 1 ) );
422     i_result2 = 0;
423
424     for( j = 0; j < i_sub_count; j++ )
425     {
426         if( result[j].psz_ext && !strcasecmp( result[j].psz_ext, "sub" ) )
427         {
428             int i;
429             for( i = 0; i < i_sub_count; i++ )
430             {
431                 if( result[i].psz_fname && result[j].psz_fname &&
432                     !strncasecmp( result[j].psz_fname, result[i].psz_fname, sizeof( result[j].psz_fname) - 4 ) && 
433                     !strcasecmp( result[i].psz_ext, "idx" ) )
434                     break;
435             }
436             if( i >= i_sub_count )
437             {
438                 result2[i_result2] = result[j].psz_fname;
439                 i_result2++;
440             }
441         }
442         else
443         {
444             result2[i_result2] = result[j].psz_fname;
445             i_result2++;
446         }
447     }
448
449     if( psz_fname_original ) free( psz_fname_original );
450     if( result ) free( result );
451     return result2;
452 }