]> git.sesse.net Git - vlc/blob - modules/access/dvd/access.c
ffeba628d899dd417cf6ac8a416af24e08132342
[vlc] / modules / access / dvd / access.c
1 /* access.c: DVD access plugin.
2  *****************************************************************************
3  * This plugins should handle all the known specificities of the DVD format,
4  * especially the 2048 bytes logical block size.
5  * It depends on:
6  *  -libdvdcss for access and unscrambling
7  *  -ifo.* for ifo parsing and analyse
8  *  -udf.* to find files
9  *****************************************************************************
10  * Copyright (C) 1998-2001 VideoLAN
11  * $Id: access.c,v 1.12 2003/03/24 17:15:29 gbazin Exp $
12  *
13  * Author: Stéphane Borel <stef@via.ecp.fr>
14  *
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 2 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program; if not, write to the Free Software
27  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
28  *****************************************************************************/
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <vlc/vlc.h>
38 #include <vlc/input.h>
39
40 #include "../../demux/mpeg/system.h"
41
42 #ifdef HAVE_UNISTD_H
43 #   include <unistd.h>
44 #endif
45
46 #include <fcntl.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <string.h>
50 #include <errno.h>
51
52 #ifdef STRNCASECMP_IN_STRINGS_H
53 #   include <strings.h>
54 #endif
55
56 #ifdef GOD_DAMN_DMCA
57 #   include "dvdcss.h"
58 #else
59 #   include <dvdcss/dvdcss.h>
60 #endif
61
62 #include "dvd.h"
63 #include "es.h"
64 #include "seek.h"
65 #include "ifo.h"
66 #include "summary.h"
67 #include "iso_lang.h"
68
69 /*****************************************************************************
70  * Local prototypes
71  *****************************************************************************/
72
73 /* called from outside */
74 static int  DVDSetArea      ( input_thread_t *, input_area_t * );
75 static int  DVDSetProgram   ( input_thread_t *, pgrm_descriptor_t * );
76 static ssize_t DVDRead      ( input_thread_t *, byte_t *, size_t );
77 static void DVDSeek         ( input_thread_t *, off_t );
78
79 static char * DVDParse( input_thread_t * );
80
81 /*
82  * Data access functions
83  */
84
85 #define DVDTell   LB2OFF( p_dvd->i_vts_start + p_dvd->i_vts_lb ) \
86                   - p_input->stream.p_selected_area->i_start
87
88 /*****************************************************************************
89  * DVDOpen: open dvd
90  *****************************************************************************/
91 int E_(DVDOpen) ( vlc_object_t *p_this )
92 {
93     input_thread_t *     p_input = (input_thread_t *)p_this;
94     char *               psz_device;
95     thread_dvd_data_t *  p_dvd;
96     input_area_t *       p_area;
97     int                  i;
98     char *               psz_dvdcss_env;
99
100     p_dvd = malloc( sizeof(thread_dvd_data_t) );
101     if( p_dvd == NULL )
102     {
103         msg_Err( p_input, "out of memory" );
104         return -1;
105     }
106     p_input->p_access_data = (void *)p_dvd;
107
108     p_input->pf_read = DVDRead;
109     p_input->pf_seek = DVDSeek;
110     p_input->pf_set_area = DVDSetArea;
111     p_input->pf_set_program = DVDSetProgram;
112
113     /* Parse command line */
114     if( !( psz_device = DVDParse( p_input ) ) )
115     {
116         free( p_dvd );
117         return -1;
118     }
119
120     /*
121      * set up input
122      */
123     p_input->i_mtu = 0;
124
125     /* override environment variable DVDCSS_METHOD with config option
126      * (FIXME: this creates a small memory leak) */
127     psz_dvdcss_env = config_GetPsz( p_input, "dvdcss-method" );
128     if( psz_dvdcss_env && *psz_dvdcss_env )
129     {
130         char *psz_env;
131
132         psz_env = malloc( strlen("DVDCSS_METHOD=") +
133                           strlen( psz_dvdcss_env ) + 1 );
134
135         if( !psz_env )
136         {
137             free( p_dvd );
138             return -1;
139         }
140
141         sprintf( psz_env, "%s%s", "DVDCSS_METHOD=", psz_dvdcss_env );
142
143         putenv( psz_env );
144     }
145     if( psz_dvdcss_env ) free( psz_dvdcss_env );
146
147     /*
148      *  get plugin ready
149      */
150     p_dvd->dvdhandle = dvdcss_open( psz_device );
151
152     /* free allocated string */
153     free( psz_device );
154
155     if( p_dvd->dvdhandle == NULL )
156     {
157         msg_Err( p_input, "dvdcss cannot open device" );
158         free( p_dvd );
159         return -1;
160     }
161
162     if( dvdcss_seek( p_dvd->dvdhandle, 0, DVDCSS_NOFLAGS ) < 0 )
163     {
164         msg_Err( p_input, "%s", dvdcss_error( p_dvd->dvdhandle ) );
165         dvdcss_close( p_dvd->dvdhandle );
166         free( p_dvd );
167         return -1;
168     }
169
170     /* Ifo allocation & initialisation */
171     if( IfoCreate( p_dvd ) < 0 )
172     {
173         msg_Err( p_input, "allcation error in ifo" );
174         dvdcss_close( p_dvd->dvdhandle );
175         free( p_dvd );
176         return -1;
177     }
178
179     if( IfoInit( p_dvd->p_ifo ) < 0 )
180     {
181         msg_Err( p_input, "fatal failure in ifo" );
182         IfoDestroy( p_dvd->p_ifo );
183         dvdcss_close( p_dvd->dvdhandle );
184         free( p_dvd );
185         return -1;
186     }
187
188     /* Set stream and area data */
189     vlc_mutex_lock( &p_input->stream.stream_lock );
190
191     p_input->stream.i_method = INPUT_METHOD_DVD;
192     p_input->stream.b_pace_control = 1;
193     p_input->stream.b_seekable = 1;
194     p_input->stream.p_selected_area->i_size = 0;
195     p_input->stream.p_selected_area->i_tell = 0;
196
197     /* Initialize ES structures */
198     input_InitStream( p_input, sizeof( stream_ps_data_t ) );
199
200 #define title_inf p_dvd->p_ifo->vmg.title_inf
201     msg_Dbg( p_input, "number of titles: %d", title_inf.i_title_nb );
202
203 #define area p_input->stream.pp_areas
204     /* We start from 1 here since the default area 0
205      * is reserved for video_ts.vob */
206     for( i = 1 ; i <= title_inf.i_title_nb ; i++ )
207     {
208         /* Titles are Program Chains */
209         input_AddArea( p_input, i, title_inf.p_attr[i-1].i_chapter_nb );
210
211         /* Absolute start offset and size
212          * We can only set that with vts ifo, so we do it during the
213          * first call to DVDSetArea */
214         area[i]->i_start = 0;
215         area[i]->i_size = 0;
216
217         /* Default Chapter */
218         area[i]->i_part = 1;
219
220         /* Offset to vts_i_0.ifo */
221         area[i]->i_plugin_data = p_dvd->p_ifo->i_start +
222                        title_inf.p_attr[i-1].i_start_sector;
223     }
224 #undef area
225
226     p_dvd->i_title = p_dvd->i_title <= title_inf.i_title_nb ?
227                      p_dvd->i_title : 1;
228 #undef title_inf
229
230     p_area = p_input->stream.pp_areas[p_dvd->i_title];
231
232     p_area->i_part = p_dvd->i_chapter <= p_area->i_part_nb ?
233                      p_dvd->i_chapter : 1;
234     p_dvd->i_chapter = 1;
235
236     p_dvd->b_new_chapter = 0;
237     p_dvd->i_audio_nb = 0;
238     p_dvd->i_spu_nb = 0;
239
240     /* set title, chapter, audio and subpic */
241     if( DVDSetArea( p_input, p_area ) < 0 )
242     {
243         vlc_mutex_unlock( &p_input->stream.stream_lock );
244         IfoDestroy( p_dvd->p_ifo );
245         dvdcss_close( p_dvd->dvdhandle );
246         free( p_dvd );
247         return -1;
248     }
249
250     vlc_mutex_unlock( &p_input->stream.stream_lock );
251
252     if( !p_input->psz_demux || !*p_input->psz_demux )
253     {
254         p_input->psz_demux = "dvdold";
255     }
256
257     return 0;
258 }
259
260 /*****************************************************************************
261  * DVDClose: close dvd
262  *****************************************************************************/
263 void E_(DVDClose) ( vlc_object_t *p_this )
264 {
265     input_thread_t * p_input = (input_thread_t *)p_this;
266     thread_dvd_data_t *p_dvd = (thread_dvd_data_t*)p_input->p_access_data;
267
268     /* This is a very nasty side-effect in the DVD plug-in : language
269      * selection here influences language selection of other streams. So
270      * unset those variables (may not be what the user wants).
271      * FIXME FIXME FIXME FIXME FIXME FIXME FIXME --Meuuh */
272     config_PutInt( p_input, "audio-channel", -1 );
273     config_PutInt( p_input, "spu-channel", -1 );
274
275     IfoDestroy( p_dvd->p_ifo );
276     dvdcss_close( p_dvd->dvdhandle );
277     free( p_dvd );
278 }
279
280 /*****************************************************************************
281  * DVDSetProgram: used to change angle
282  *****************************************************************************/
283 static int DVDSetProgram( input_thread_t    * p_input,
284                           pgrm_descriptor_t * p_program )
285 {
286     if( p_input->stream.p_selected_program != p_program )
287     {
288         thread_dvd_data_t *  p_dvd;
289         int                  i_angle;
290         vlc_value_t          val;
291
292         p_dvd   = (thread_dvd_data_t*)(p_input->p_access_data);
293         i_angle = p_program->i_number;
294
295         /* DVD is actually mono-program: we only need the current angle
296          * number, so copy the data between programs */
297         memcpy( p_program,
298                 p_input->stream.p_selected_program,
299                 sizeof(pgrm_descriptor_t) );
300         p_program->i_number                = i_angle;
301         p_input->stream.p_selected_program = p_program;
302
303 #define title \
304     p_dvd->p_ifo->vts.title_unit.p_title[p_dvd->i_title_id-1].title
305         if( title.p_cell_play[p_dvd->i_prg_cell].i_category & 0xf000 )
306         {
307             if( ( p_program->i_number - p_dvd->i_angle ) < 0 )
308             {
309                 /* we have to go backwards */
310                 p_dvd->i_map_cell = 0;
311             }
312             p_dvd->i_prg_cell += ( p_program->i_number - p_dvd->i_angle );
313             p_dvd->i_map_cell =  CellPrg2Map( p_dvd );
314             p_dvd->i_map_cell += p_dvd->i_angle_cell;
315             p_dvd->i_vts_lb   =  CellFirstSector( p_dvd );
316             p_dvd->i_last_lb  =  CellLastSector( p_dvd );
317             p_dvd->i_angle    =  p_program->i_number;
318         }
319         else
320         {
321             p_dvd->i_angle    =  p_program->i_number;
322         }
323 #undef title
324         msg_Dbg( p_input, "angle %d selected", p_dvd->i_angle );
325
326         /* Update the navigation variables without triggering a callback */
327         val.i_int = p_program->i_number;
328         var_Change( p_input, "program", VLC_VAR_SETVALUE, &val );
329     }
330
331     return 0;
332 }
333
334 /*****************************************************************************
335  * DVDSetArea: initialize input data for title x, chapter y.
336  * It should be called for each user navigation request.
337  *****************************************************************************
338  * Take care that i_title starts from 0 (vmg) and i_chapter start from 1.
339  * Note that you have to take the lock before entering here.
340  *****************************************************************************/
341 #define vmg p_dvd->p_ifo->vmg
342 #define vts p_dvd->p_ifo->vts
343
344 static void DVDFlushStream( input_thread_t * p_input )
345 {
346     if( p_input->stream.pp_programs != NULL )
347     {
348         /* We don't use input_EndStream here since
349          * we keep area structures */
350         while( p_input->stream.i_es_number )
351         {
352             input_DelES( p_input, p_input->stream.pp_es[0] );
353         }
354
355         while( p_input->stream.i_pgrm_number )
356         {
357             input_DelProgram( p_input, p_input->stream.pp_programs[0] );
358         }
359
360         if( p_input->stream.pp_selected_es )
361         {
362             free( p_input->stream.pp_selected_es );
363             p_input->stream.pp_selected_es = NULL;
364         }
365         p_input->stream.i_selected_es_number = 0;
366     }
367
368     return;
369 }
370
371 static int DVDReadAngle( input_thread_t * p_input )
372 {
373     thread_dvd_data_t * p_dvd;
374     int                 i_angle_nb;
375     int                 i;
376
377     p_dvd      = (thread_dvd_data_t*)(p_input->p_access_data);
378     i_angle_nb = vmg.title_inf.p_attr[p_dvd->i_title-1].i_angle_nb;
379
380     input_AddProgram( p_input, 1, sizeof( stream_ps_data_t ) );
381     p_input->stream.p_selected_program = p_input->stream.pp_programs[0];
382
383     for( i = 1 ; i < i_angle_nb ; i++ )
384     {
385         input_AddProgram( p_input, i+1, 0 );
386     }
387
388     return i_angle_nb;
389 }
390
391 static int DVDSetArea( input_thread_t * p_input, input_area_t * p_area )
392 {
393     thread_dvd_data_t *  p_dvd;
394     vlc_value_t          val;
395
396     p_dvd = (thread_dvd_data_t*)(p_input->p_access_data);
397
398     /* we can't use the interface slider until initilization is complete */
399     p_input->stream.b_seekable = 0;
400
401     if( p_area != p_input->stream.p_selected_area )
402     {
403         int     i_vts_title;
404         u32     i_first;
405         u32     i_last;
406         unsigned int i;
407
408         /* Reset the Chapter position of the old title */
409         p_input->stream.p_selected_area->i_part = 1;
410         p_input->stream.p_selected_area         = p_area;
411
412         /*
413          *  We have to load all title information
414          */
415
416         /* title number as it appears in the interface list */
417         p_dvd->i_title      = p_area->i_id;
418         p_dvd->i_chapter_nb = p_area->i_part_nb;
419
420         if( IfoTitleSet( p_dvd->p_ifo, p_dvd->i_title ) < 0 )
421         {
422             msg_Err( p_input, "fatal error in vts ifo" );
423             free( p_dvd );
424             return -1;
425         }
426
427         /* title position inside the selected vts */
428         i_vts_title       = vmg.title_inf.p_attr[p_dvd->i_title-1].i_title_num;
429         p_dvd->i_title_id =
430             vts.title_inf.p_title_start[i_vts_title-1].i_title_id;
431
432         msg_Dbg( p_input, "title %d vts_title %d pgc %d",
433                           p_dvd->i_title, i_vts_title, p_dvd->i_title_id );
434
435         /* title set offset XXX: convert to block values */
436         p_dvd->i_vts_start =
437             vts.i_pos + vts.manager_inf.i_title_vob_start_sector;
438
439         /* last cell */
440         p_dvd->i_prg_cell = -1 +
441             vts.title_unit.p_title[p_dvd->i_title_id-1].title.i_cell_nb;
442         p_dvd->i_map_cell = 0;
443         p_dvd->i_map_cell = CellPrg2Map( p_dvd );
444         i_last            = CellLastSector( p_dvd );
445
446         /* first cell */
447         p_dvd->i_prg_cell   = 0;
448         p_dvd->i_map_cell   = 0;
449         p_dvd->i_angle_cell = 0;
450         p_dvd->i_map_cell   = CellPrg2Map    ( p_dvd );
451         p_dvd->i_vts_lb     = CellFirstSector( p_dvd );
452         p_dvd->i_last_lb    = CellLastSector ( p_dvd );
453
454         /* Force libdvdcss to check its title key.
455          * It is only useful for title cracking method. Methods using the
456          * decrypted disc key are fast enough to check the key at each seek */
457         i_first = dvdcss_seek( p_dvd->dvdhandle,
458                                p_dvd->i_vts_start + p_dvd->i_vts_lb,
459                                DVDCSS_SEEK_KEY );
460         if( i_first < 0 )
461         {
462             msg_Err( p_input, "%s", dvdcss_error( p_dvd->dvdhandle ) );
463             return -1;
464         }
465
466         /* Area definition */
467         p_input->stream.p_selected_area->i_start = LB2OFF( i_first );
468         p_input->stream.p_selected_area->i_size  =
469                                         LB2OFF( i_last + 1 - p_dvd->i_vts_lb );
470
471         /* Destroy obsolete ES by reinitializing programs */
472         DVDFlushStream( p_input );
473
474         /* Angle management: angles are handled through programs */
475         p_dvd->i_angle_nb = DVDReadAngle( p_input );
476         if( ( p_dvd->i_angle <= 0 ) || p_dvd->i_angle > p_dvd->i_angle_nb )
477         {
478             p_dvd->i_angle = 1;
479         }
480
481         DVDSetProgram( p_input,
482                        p_input->stream.pp_programs[p_dvd->i_angle-1] );
483
484         msg_Dbg( p_input, "title first %i, last %i, size %i",
485                           i_first, i_last, i_last + 1 - p_dvd->i_vts_lb );
486         IfoPrintTitle( p_dvd );
487
488         /* No PSM to read in DVD mode, we already have all information */
489         p_input->stream.p_selected_program->b_is_ok = 1;
490
491         /* Find all ES in title with ifo data */
492         DVDReadVideo( p_input );
493         DVDReadAudio( p_input );
494         DVDReadSPU  ( p_input );
495
496         if( p_input->p_demux )
497         {
498             DVDLaunchDecoders( p_input );
499         }
500
501         /* Update the navigation variables without triggering a callback */
502         val.i_int = p_area->i_id;
503         var_Change( p_input, "title", VLC_VAR_SETVALUE, &val );
504         var_Change( p_input, "chapter", VLC_VAR_CLEARCHOICES, NULL );
505         for( i = 1; i <= p_area->i_part_nb; i++ )
506         {
507             val.i_int = i;
508             var_Change( p_input, "chapter", VLC_VAR_ADDCHOICE, &val );
509         }
510
511     } /* i_title >= 0 */
512     else
513     {
514         p_area = p_input->stream.p_selected_area;
515     }
516
517     /* Chapter selection */
518     p_dvd->i_chapter = DVDSetChapter( p_dvd, p_area->i_part );
519
520     p_input->stream.p_selected_area->i_tell = DVDTell;
521
522     /* warn interface that something has changed */
523     p_input->stream.b_seekable = 1;
524     p_input->stream.b_changed  = 1;
525
526     /* Update the navigation variables without triggering a callback */
527     val.i_int = p_area->i_part;
528     var_Change( p_input, "chapter", VLC_VAR_SETVALUE, &val );
529
530     return 0;
531 }
532 #undef vts
533 #undef vmg
534
535 #define title \
536     p_dvd->p_ifo->vts.title_unit.p_title[p_dvd->i_title_id-1].title
537
538 /*****************************************************************************
539  * DVDRead: reads data packets.
540  *****************************************************************************
541  * Returns -1 in case of error, 0 in case of EOF, otherwise the number of
542  * bytes.
543  *****************************************************************************/
544 static ssize_t DVDRead( input_thread_t * p_input,
545                         byte_t * p_buffer, size_t i_count )
546 {
547     thread_dvd_data_t *     p_dvd;
548     int                     i_read;
549     int                     i_blocks;
550     int                     i_block_once = 0;
551
552     p_dvd = (thread_dvd_data_t *)(p_input->p_access_data);
553
554     i_read = 0;
555     i_blocks = OFF2LB(i_count);
556
557     while( i_blocks )
558     {
559         i_block_once = LbMaxOnce( p_dvd );
560         if( i_block_once > i_blocks )
561         {
562             i_block_once = i_blocks;
563         }
564         else if( i_block_once <= 0 )
565         {
566             /* EOT */
567             break;
568         }
569
570         if( i_block_once != dvdcss_read( p_dvd->dvdhandle, p_buffer,
571                                          i_block_once, DVDCSS_READ_DECRYPT ) )
572         {
573             return -1;
574         }
575
576         i_blocks -= i_block_once;
577         i_read += i_block_once;
578         p_buffer += LB2OFF( i_block_once );
579
580         /* Update global position */
581         p_dvd->i_vts_lb += i_block_once;
582     }
583
584     vlc_mutex_lock( &p_input->stream.stream_lock );
585
586     if( p_dvd->b_new_chapter )
587     {
588         p_input->stream.p_selected_area->i_part = p_dvd->i_chapter;
589         p_dvd->b_new_chapter                    = 0;
590     }
591
592     if( ( p_input->stream.p_selected_area->i_tell + LB2OFF( i_read )
593             >= p_input->stream.p_selected_area->i_size )
594        || ( i_block_once  <= 0 ) )
595     {
596         if( ( p_dvd->i_title + 1 ) >= p_input->stream.i_area_nb )
597         {
598             /* EOF */
599             vlc_mutex_unlock( &p_input->stream.stream_lock );
600             return 0;
601         }
602
603         /* EOT */
604         msg_Dbg( p_input, "new title" );
605         p_dvd->i_title++;
606         DVDSetArea( p_input, p_input->stream.pp_areas[p_dvd->i_title] );
607     }
608
609     vlc_mutex_unlock( &p_input->stream.stream_lock );
610
611     return LB2OFF( i_read );
612 }
613
614 /*****************************************************************************
615  * DVDSeek : Goes to a given position on the stream.
616  *****************************************************************************
617  * This one is used by the input and translate chronological position from
618  * input to logical position on the device.
619  * The lock should be taken before calling this function.
620  *****************************************************************************/
621 static void DVDSeek( input_thread_t * p_input, off_t i_off )
622 {
623     thread_dvd_data_t *     p_dvd;
624
625     p_dvd = ( thread_dvd_data_t * )(p_input->p_access_data);
626
627     vlc_mutex_lock( &p_input->stream.stream_lock );
628     p_dvd->i_vts_lb = OFF2LB(i_off + p_input->stream.p_selected_area->i_start)
629                        - p_dvd->i_vts_start;
630     vlc_mutex_unlock( &p_input->stream.stream_lock );
631
632     p_dvd->i_prg_cell = Lb2CellPrg( p_dvd );
633     p_dvd->i_map_cell = Lb2CellMap( p_dvd );
634
635     if( CellIsInterleaved( p_dvd ) )
636     {
637         /* if we're inside a multi-angle zone, we have to choose i_sector
638          * in the current angle ; we can't do it all the time since cells
639          * can be very wide out of such zones */
640         p_dvd->i_vts_lb = CellFirstSector( p_dvd );
641     }
642
643     p_dvd->i_last_lb  = CellLastSector( p_dvd );
644     p_dvd->i_chapter  = CellPrg2Chapter( p_dvd );
645
646     if( dvdcss_seek( p_dvd->dvdhandle, p_dvd->i_vts_start + p_dvd->i_vts_lb,
647                      DVDCSS_SEEK_MPEG ) < 0 )
648     {
649         msg_Err( p_input, "%s", dvdcss_error( p_dvd->dvdhandle ) );
650         p_input->b_error = 1;
651         return;
652     }
653
654     vlc_mutex_lock( &p_input->stream.stream_lock );
655     p_input->stream.p_selected_area->i_part = p_dvd->i_chapter;
656     p_input->stream.p_selected_area->i_tell = DVDTell;
657     vlc_mutex_unlock( &p_input->stream.stream_lock );
658
659     msg_Dbg( p_input, "program cell: %d cell: %d chapter: %d tell "I64Fd,
660              p_dvd->i_prg_cell, p_dvd->i_map_cell, p_dvd->i_chapter, DVDTell );
661
662     return;
663 }
664
665 /*****************************************************************************
666  * DVDParse: parse command line
667  *****************************************************************************/
668 static char * DVDParse( input_thread_t * p_input )
669 {
670     thread_dvd_data_t *  p_dvd;
671     struct stat          stat_info;
672     char *               psz_parser;
673     char *               psz_device;
674     char *               psz_raw;
675     char *               psz_next;
676     vlc_bool_t           b_options = 0;
677     int                  i_title = 1;
678     int                  i_chapter = 1;
679     int                  i_angle = 1;
680     int                  i;
681
682     p_dvd = (thread_dvd_data_t*)(p_input->p_access_data);
683
684 #ifdef WIN32
685     /* On Win32 we want the DVD access plugin to be explicitly requested,
686      * we end up with lots of problems otherwise */
687     if( !p_input->psz_access || !*p_input->psz_access ) return NULL;
688 #endif
689
690     psz_parser = psz_device = strdup( p_input->psz_name );
691     if( !psz_parser )
692     {
693         return NULL;
694     }
695
696     /* Parse input string :
697      * [device][@rawdevice][@[title][,[chapter][,angle]]] */
698     while( *psz_parser && *psz_parser != '@' )
699     {
700         psz_parser++;
701     }
702
703     if( *psz_parser == '@' )
704     {
705         /* Maybe found raw device or option list */
706         *psz_parser = '\0';
707         psz_raw = ++psz_parser;
708     }
709     else
710     {
711         psz_raw = "";
712     }
713
714     if( *psz_parser && !strtol( psz_parser, NULL, 10 ) )
715     {
716         /* what we've found is either a raw device or a partial option
717          * list e.g. @,29 or both a device and a list ; search end of string */
718         while( *psz_parser && *psz_parser != '@' )
719         {
720             psz_parser++;
721         }
722
723         if( *psz_parser == '@' )
724         {
725             /* found end of raw device, and beginning of options */
726             *psz_parser = '\0';
727             ++psz_parser;
728             b_options = 1;
729         }
730         else
731         {
732             psz_parser = psz_raw + 1;
733             for( i=0 ; i<3 ; i++ )
734             {
735                 if( !*psz_parser )
736                 {
737                     /* we have only a raw device */
738                     break;
739                 }
740                 if( strtol( psz_parser, NULL, 10 ) )
741                 {
742                     /* we have only a partial list of options, no device */
743                     psz_parser = psz_raw;
744                     psz_raw = "";
745                     b_options = 1;
746                     break;
747                 }
748                 psz_parser++;
749             }
750         }
751     }
752     else
753     {
754         /* found beginning of options ; no raw device specified */
755         psz_raw = "";
756         b_options = 1;
757     }
758
759     if( b_options )
760     {
761         /* Found options */
762         i_title = (int)strtol( psz_parser, &psz_next, 10 );
763         if( *psz_next )
764         {
765             psz_parser = psz_next + 1;
766             i_chapter = (int)strtol( psz_parser, &psz_next, 10 );
767             if( *psz_next )
768             {
769                 i_angle = (int)strtol( psz_next + 1, NULL, 10 );
770             }
771         }
772
773         p_dvd->i_title = i_title ? i_title : 1;
774         p_dvd->i_chapter = i_chapter ? i_chapter : 1;
775         p_dvd->i_angle = i_angle ? i_angle : 1;
776     }
777
778     if( *psz_raw )
779     {
780         if( *psz_raw )
781         {
782             /* check the raw device */
783             if( stat( psz_raw, &stat_info ) == -1 )
784             {
785                 msg_Warn( p_input, "cannot stat() raw device `%s' (%s)",
786                                    psz_raw, strerror(errno));
787                 /* put back '@' */
788                 *(psz_raw - 1) = '@';
789                 psz_raw = "";
790             }
791             else
792             {
793                 char * psz_env;
794
795 #ifndef WIN32
796                 if( !S_ISCHR(stat_info.st_mode) )
797                 {
798                     msg_Warn( p_input, "raw device %s is"
799                               " not a valid char device", psz_raw );
800                     /* put back '@' */
801                     *(psz_raw - 1) = '@';
802                     psz_raw = "";
803                 }
804                 else
805 #endif
806                 {
807                     psz_env = malloc( strlen("DVDCSS_RAW_DEVICE=")
808                                     + strlen( psz_raw ) + 1 );
809                     sprintf( psz_env, "DVDCSS_RAW_DEVICE=%s", psz_raw );
810                     putenv( psz_env );
811                 }
812             }
813         }
814         else
815         {
816             psz_raw = "";
817         }
818     }
819
820     if( !*psz_device )
821     {
822         free( psz_device );
823
824         if( !p_input->psz_access )
825         {
826             /* no device and no access specified: we probably don't want DVD */
827             return NULL;
828         }
829         psz_device = config_GetPsz( p_input, "dvd" );
830     }
831
832 #ifndef WIN32
833     /* check block device */
834     if( stat( psz_device, &stat_info ) == -1 )
835     {
836         msg_Warn( p_input, "cannot stat() device `%s' (%s)",
837                   psz_device, strerror(errno));
838         free( psz_device );
839         return NULL;
840     }
841
842     if( !S_ISBLK(stat_info.st_mode) && !S_ISCHR(stat_info.st_mode) )
843     {
844         msg_Warn( p_input,
845                   "dvd module discarded (not a valid block device)" );
846         free( psz_device );
847         return NULL;
848     }
849 #endif
850
851     msg_Dbg( p_input, "dvd=%s raw=%s title=%d chapter=%d angle=%d",
852                       psz_device, psz_raw, p_dvd->i_title,
853                       p_dvd->i_chapter, p_dvd->i_angle );
854
855     return psz_device;
856 }