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