]> git.sesse.net Git - vlc/blob - src/input/subtitles.c
FSF address change.
[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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 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( psz_fname == NULL ) return NULL;
292
293     if( !strncmp( psz_fname, "file://", 7 ) )
294     {
295         psz_fname += 7;
296     }
297
298     /* extract filename & dirname from psz_fname */
299     tmp = strrchr( psz_fname, DIRECTORY_SEPARATOR );
300     if( tmp )
301     {
302         int dirlen = 0;
303
304         f_fname = malloc( strlen(tmp) );
305         if( f_fname )
306             strcpy( f_fname, tmp+1 ); // we skip the seperator, so it will still fit in the allocated space
307         dirlen = strlen(psz_fname) - strlen(tmp) + 1; // add the seperator
308         f_dir = malloc( dirlen + 1 );
309         if( f_dir )
310         {
311             strncpy( f_dir, psz_fname, dirlen );
312             f_dir[dirlen] = 0;
313         }
314     }
315     else
316     {
317         /* FIXME: we should check the CWD here */
318         /* f_fname = strdup( psz_fname ); */
319         if( psz_fname_original ) free( psz_fname_original );
320         return NULL;
321     }
322
323     i_fname_len = strlen( f_fname );
324     f_fname_noext = malloc(i_fname_len + 1);
325     f_fname_trim = malloc(i_fname_len + 1 );
326
327     strcpy_strip_ext( f_fname_noext, f_fname );
328     strcpy_trim( f_fname_trim, f_fname_noext );
329
330     result = (subfn*)malloc( sizeof(subfn) * MAX_SUBTITLE_FILES );
331     if( result )
332         memset( result, 0, sizeof(subfn) * MAX_SUBTITLE_FILES );
333
334     var_Get( p_this, "sub-autodetect-fuzzy", &fuzzy );
335
336     tmp_subdirs = paths_to_list( f_dir, psz_path );
337     subdirs = tmp_subdirs;
338
339     for( j = -1; (j == -1) || ( (j >= 0) && (subdirs != NULL) && (*subdirs != NULL) );
340          j++)
341     {
342         pp_dir_content = NULL;
343         i_dir_content = 0;
344
345         if( j < 0 && f_dir == NULL )
346             continue;
347
348         /* parse psz_src dir */  
349         if( ( i_dir_content = scandir( j < 0 ? f_dir : *subdirs, &pp_dir_content, Filter,
350                                 NULL ) ) != -1 )
351         {
352             int a;
353
354             msg_Dbg( p_this, "looking for a subtitle file in %s", j < 0 ? f_dir : *subdirs );
355             for( a = 0; a < i_dir_content; a++ )
356             {
357                 int i_prio = 0;
358                 struct dirent *p_dir_content = pp_dir_content[a];
359                 char *psz_inUTF8 = FromLocale( p_dir_content->d_name );
360                 char *p_fixed_name = vlc_fix_readdir_charset( p_this, psz_inUTF8 );
361
362                 LocaleFree( psz_inUTF8 );
363
364                 /* retrieve various parts of the filename */
365                 strcpy_strip_ext( tmp_fname_noext, p_fixed_name );
366                 strcpy_get_ext( tmp_fname_ext, p_fixed_name );
367                 strcpy_trim( tmp_fname_trim, tmp_fname_noext );
368
369                 if( !i_prio && !strcmp( tmp_fname_trim, f_fname_trim ) )
370                 {
371                     /* matches the movie name exactly */
372                     i_prio = 4;
373                 }
374                 if( !i_prio &&
375                     ( tmp = strstr( tmp_fname_trim, f_fname_trim ) ) )
376                 {
377                     /* contains the movie name */
378                     tmp += strlen( f_fname_trim );
379                     if( whiteonly( tmp ) )
380                     {
381                         /* chars in front of the movie name */
382                         i_prio = 2;
383                     }
384                     else
385                     {
386                         /* chars after (and possibly in front of)
387                          * the movie name */
388                         i_prio = 3;
389                     }
390                 }
391                 if( !i_prio )
392                 {
393                     /* doesn't contain the movie name */
394                     if( j == 0 ) i_prio = 1;
395                 }
396                 if( i_prio >= fuzzy.i_int )
397                 {
398                     FILE *f;
399                     char *tmpresult;
400
401                     asprintf( &tmpresult, "%s%s", j < 0 ? f_dir : *subdirs, p_fixed_name );
402                     msg_Dbg( p_this, "autodetected subtitle: %s with priority %d", p_fixed_name, i_prio );
403                     if( ( f = fopen( tmpresult, "rt" ) ) )
404                     {
405                         fclose( f );
406                         result[i_sub_count].priority = i_prio;
407                         result[i_sub_count].psz_fname = tmpresult;
408                         result[i_sub_count].psz_ext = strdup(tmp_fname_ext);
409                         i_sub_count++;
410                     } else free( tmpresult );
411                 }
412                 if( i_sub_count >= MAX_SUBTITLE_FILES ) break;
413                 free( p_fixed_name );
414
415                 for( a = 0; a < i_dir_content; a++ )
416                     if( pp_dir_content[a] ) free( pp_dir_content[a] );
417                 if( pp_dir_content ) free( pp_dir_content );
418             }
419         }
420         if( j >= 0 ) free( *subdirs++ );
421     }
422
423     if( tmp_subdirs )   free( tmp_subdirs );
424     if( f_fname_trim )  free( f_fname_trim );
425     if( f_fname_noext ) free( f_fname_noext );
426     if( f_fname ) free( f_fname );
427     if( f_dir )   free( f_dir );
428
429     qsort( result, i_sub_count, sizeof( subfn ), compare_sub_priority );
430
431     result2 = (char**)malloc( sizeof(char*) * ( i_sub_count + 1 ) );
432     if( result2 )
433         memset( result2, 0, sizeof(char*) * ( i_sub_count + 1 ) );
434     i_result2 = 0;
435
436     for( j = 0; j < i_sub_count; j++ )
437     {
438         if( result[j].psz_ext && !strcasecmp( result[j].psz_ext, "sub" ) )
439         {
440             int i;
441             for( i = 0; i < i_sub_count; i++ )
442             {
443                 if( result[i].psz_fname && result[j].psz_fname &&
444                     !strncasecmp( result[j].psz_fname, result[i].psz_fname, sizeof( result[j].psz_fname) - 4 ) && 
445                     !strcasecmp( result[i].psz_ext, "idx" ) )
446                     break;
447             }
448             if( i >= i_sub_count )
449             {
450                 result2[i_result2] = result[j].psz_fname;
451                 i_result2++;
452             }
453         }
454         else
455         {
456             result2[i_result2] = result[j].psz_fname;
457             i_result2++;
458         }
459     }
460
461     if( psz_fname_original ) free( psz_fname_original );
462     if( result ) free( result );
463     return result2;
464 }