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