diff options
author | Klaus Schmidinger <kls (at) cadsoft (dot) de> | 2001-01-18 18:00:00 +0100 |
---|---|---|
committer | Klaus Schmidinger <kls (at) cadsoft (dot) de> | 2001-01-18 18:00:00 +0100 |
commit | 3fe3c15d5db9c1f3982ffe6dac1ae4ad56d1664d (patch) | |
tree | e1c4e489899c119fdfaad6f6bdf9bde988616df2 | |
parent | 9aa2cda494d7af2733362de78234441a25959e86 (diff) | |
download | vdr-patch-lnbsharing-vdr-0.70.tar.gz vdr-patch-lnbsharing-vdr-0.70.tar.bz2 |
Version 0.70vdr-0.70
- VDR now requires driver version 0.8.1 or higher.
- Recordings are now saved in PES mode. Note that you now need to install the
driver *WITHOUT* 'outstream=0'! This is the default when you 'make insmod' in
the DVB/driver directory.
Old recordings (in AV_PES mode) can still be replayed (as long as the driver
still supports replaying AV_PES files). The only limitation with this is that
in fast forward/back mode the picture may be slightly distorted and there may
be sound fragments.
- The EPG data is now dumped into the file /video/epg.data every ten minutes.
Use the Perl script 'epg2html.pl' to convert the raw EPG data into a simple
HTML programme listing.
- Fixed handling of channel switching with the "Blue" button in the "What's on
now/next?" menus.
- Fixed saving the MarginStop setup parameter.
- Fixed missing initialization in cConfig.
- Implemented "On Disk Editing".
- There is no more default 'timers.conf' file.
- Added Italian language texts (thanks to Alberto Carraro).
- Fixed starting a replay session when the program is currently in "transfer
mode".
- Fixed setting/modifying timers via SVDRP with empty summary fields.
- Fixed a problem with recordings that have a single quote character in their
name (this is now mapped to 0x01).
- Changed the value for Diseqc to '0' in the default 'channels.conf'.
- Fixed displaying channels and recording status in the RCU's LED display when
a recording is interrupted due to higher priority.
- Implemented safe writing of config files (first writes into a temporary file
and then renames it).
- In case the video data stream is broken the log message will come only every
5 seconds.
- The current channel is now saved in the 'setup.conf' file when VDR is cancelled,
and will be restored next time it is started (thanks to Deti Fliegl).
- The EIT scanning thread is now locked when switching channels to avoid problems.
- Encrypted channels can now be selected even without knowing the PNR (however, it
is still necessary for the EPG info).
-rw-r--r-- | CONTRIBUTORS | 6 | ||||
-rw-r--r-- | FORMATS | 23 | ||||
-rw-r--r-- | HISTORY | 38 | ||||
-rw-r--r-- | INSTALL | 17 | ||||
-rw-r--r-- | MANUAL | 46 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | TODO | 3 | ||||
-rw-r--r-- | channels.conf | 291 | ||||
-rw-r--r-- | config.c | 29 | ||||
-rw-r--r-- | config.h | 73 | ||||
-rw-r--r-- | dvb.c.071.diff | 100 | ||||
-rw-r--r-- | dvbapi.c | 1533 | ||||
-rw-r--r-- | dvbapi.h | 64 | ||||
-rw-r--r-- | dvbosd.c | 12 | ||||
-rw-r--r-- | dvbosd.h | 3 | ||||
-rw-r--r-- | eit.c | 58 | ||||
-rw-r--r-- | eit.h | 5 | ||||
-rw-r--r-- | epg2html.pl | 96 | ||||
-rw-r--r-- | i18n.c | 140 | ||||
-rw-r--r-- | interface.c | 8 | ||||
-rw-r--r-- | interface.h | 3 | ||||
-rw-r--r-- | menu.c | 265 | ||||
-rw-r--r-- | menu.h | 11 | ||||
-rw-r--r-- | osd.h | 3 | ||||
-rw-r--r-- | recording.c | 112 | ||||
-rw-r--r-- | recording.h | 25 | ||||
-rw-r--r-- | remote.c | 6 | ||||
-rw-r--r-- | svdrp.c | 3 | ||||
-rw-r--r-- | thread.c | 36 | ||||
-rw-r--r-- | thread.h | 7 | ||||
-rw-r--r-- | timers.conf | 14 | ||||
-rw-r--r-- | tools.c | 119 | ||||
-rw-r--r-- | tools.h | 33 | ||||
-rw-r--r-- | vdr.c | 15 | ||||
-rw-r--r-- | videodir.c | 19 | ||||
-rw-r--r-- | videodir.h | 3 |
37 files changed, 2056 insertions, 1173 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 279706d..ee44ada 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -43,3 +43,9 @@ Matthias Schniedermeyer <ms@citd.de> Miha Setina <mihasetina@softhome.net> for translating the OSD texts to the Slovenian language. + +Alberto Carraro <bertocar@tin.it> + for translating the OSD texts to the Italian language. + +Deti Fliegl <deti@fliegl.de> + for implementing the 'CurrentChannel' setup parameter. @@ -90,3 +90,26 @@ Video Disk Recorder File Formats CPU status : /usr/loval/bin/cpustatus 2>&1 Disk space : df -h | grep '/video' | awk '{ print 100 - $5 "% free"; }' +* marks.vdr + + This file (if present in a recording directory) contains the editing marks + defined for this recording. + + Each line contains the definition of one mark in the following format: + + hh:mm:ss.ff comment + + where 'hh:mm:ss.ff' is a frame position within the recording, given as "hours, + minutes, seconds and (optional) frame number". 'comment' can be any string + and may be used to describe this mark. If present, 'comment' must be separated + from the frame position by at least one blank. + + The lines in this file need not necessarily appear in the correct temporal + sequence, they will be automatically sorted by time index. + + CURRENT RESTRICTIONS: + + - the 'comment' is currently not used by VDR + - marks must have a frame number, and that frame MUST be an I-frame (this + means that only marks generated by VDR itself can be used, since they + will always be guaranteed to mark I-frames). @@ -311,3 +311,41 @@ Video Disk Recorder Revision History can receive a certain channel on the primary interface. This is currently in an early state and may still cause some problems, but it appears to work nice already. + +2001-01-18: Version 0.70 + +- VDR now requires driver version 0.8.1 or higher. +- Recordings are now saved in PES mode. Note that you now need to install the + driver *WITHOUT* 'outstream=0'! This is the default when you 'make insmod' in + the DVB/driver directory. + Old recordings (in AV_PES mode) can still be replayed (as long as the driver + still supports replaying AV_PES files). The only limitation with this is that + in fast forward/back mode the picture may be slightly distorted and there may + be sound fragments. +- The EPG data is now dumped into the file /video/epg.data every ten minutes. + Use the Perl script 'epg2html.pl' to convert the raw EPG data into a simple + HTML programme listing. +- Fixed handling of channel switching with the "Blue" button in the "What's on + now/next?" menus. +- Fixed saving the MarginStop setup parameter. +- Fixed missing initialization in cConfig. +- Implemented "On Disk Editing". +- There is no more default 'timers.conf' file. +- Added Italian language texts (thanks to Alberto Carraro). +- Fixed starting a replay session when the program is currently in "transfer + mode". +- Fixed setting/modifying timers via SVDRP with empty summary fields. +- Fixed a problem with recordings that have a single quote character in their + name (this is now mapped to 0x01). +- Changed the value for Diseqc to '0' in the default 'channels.conf'. +- Fixed displaying channels and recording status in the RCU's LED display when + a recording is interrupted due to higher priority. +- Implemented safe writing of config files (first writes into a temporary file + and then renames it). +- In case the video data stream is broken the log message will come only every + 5 seconds. +- The current channel is now saved in the 'setup.conf' file when VDR is cancelled, + and will be restored next time it is started (thanks to Deti Fliegl). +- The EIT scanning thread is now locked when switching channels to avoid problems. +- Encrypted channels can now be selected even without knowing the PNR (however, it + is still necessary for the EPG info). @@ -15,13 +15,11 @@ If you have the DVB driver source in a different location you will have to change the definition of DVBDIR in the Makefile. -This program requires the card driver version 0.71 or higher -to work properly. Currently you need to load the dvb.o module with -option outstream=0, so your insmod statement should read -'insmod dvb.o outstream=0'. This is necessary because 'vdr' works -with AV_PES data and will change once it has been modified to work -directly with MPEG2. You also need to apply the patch 'dvb.c.071.diff' -for the On Screen Display to work properly. +This program requires the card driver version 0.8.1 or higher +to work properly. You need to load the dvb.o module *without* option +'outstream=0' (previous versions of VDR required this option to have +the driver supply the data in AV_PES format; as of version 0.70 VDR +works with PES format). After extracting the package, change into the VDR directory and type 'make'. This should produce an executable file @@ -39,7 +37,7 @@ following values 'make' call to activate the respective control mode: REMOTE=RCU control via the "Remote Control Unit" receiver (see http://www.cadsoft.de/people/kls/vdr/remote.htm) REMOTE=LIRC control via the "Linux Infrared Remote Control" - (see http://fsinfo.cs.uni-sb.de/~columbus/lirc) + (see http://www.lirc.org) Adding "DEBUG_OSD=1" will use the PC screen (or current window) to display texts instead of the DVB card's on-screen display @@ -168,6 +166,5 @@ into learning mode. If the program has been compiled with 'REMOTE=LIRC', no 'keys.conf' file will be used. Instead, the key names as listed in the source file 'config.c' -must be used when setting up LIRC. See http://www2.arnes.si/~mthale1 for -more about LIRC. +must be used when setting up LIRC. See http://www.lirc.org for more about LIRC. @@ -174,6 +174,52 @@ Video Disk Recorder User's Manual used to easily delete a recording after watching it, or to switch to a different recording. +* Editing a Recording + + While in Replay mode, the following keys can be used to manipulate editing + marks: + + - 0 Toggles an editing mark. If the mark indicator shows a red triangle, + the current mark is deleted. Otherwise a new mark is set at the + current position. + - 4, 6 Move an editing mark back and forward. You need to first jump to + an editing mark for this to work. + - 7, 9 Jump back and forward between editing marks. Replay goes into still + mode after jumping to a mark. + - 8 Positions replay at a point 3 seconds before the current or next + "start" mark and starts replay. + - 2 Start the actual cutting process. + + Editing marks are represented by black, vertical lines in the progress display. + A small black triangle at the top of the mark means that this is a "start" + mark, and a triangle at the bottom means that this is an "end" mark. + The cutting process will save all video data between "start" and "end" marks + into a new file (the original recording remains untouched). The new file will + have the same name as the original recording, preceeded with a '%' character + (imagine the '%' somehow looking like a pair of scissors ;-). Red bars in the + progress display indicate which video sequences will be saved by the cutting + process. + + The video sequences to be saved by the cutting process are determined by an + "even/odd" algorithm. This means that every odd numbered editing mark (i.e. + 1, 3, 5,...) represents a "start" mark, while every even numbered mark (2, 4, + 6,...) is an "end" mark. Inserting or toggling a mark on or off automatically + adjusts the sequence to the right side of that mark. + + Use the keys described under "Replay Control" to position to, e.g., the + beginning and end of commercial breaks and press the '0' key to set the + necessary editing marks. After that you may want to use the '7' and '9' + keys to jump to each mark and maybe use the '4' and '6' keys to fine tune + them. Once all marks are in place, press '2' to start the actual cutting + process, which will run as a background process. When replaying the edited + version of the recording you can use the '8' key to jump to a point just + before the next cut and have a look at the resulting sequence. + + Currently editing marks can only be set at I-frames, which typically is + every 12th frame. So editing can be done with a resolution of roughly half + a second. A "start" mark marks the first frame of a resulting video + sequence, and an "end" mark marks the last frame of that sequence. + * Programming the Timer Use the "Timer" menu to maintain your list of timer controlled recordings. @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.16 2000/11/18 14:58:10 kls Exp $ +# $Id: Makefile 1.18 2001/01/13 12:26:43 kls Exp $ DVBDIR = ../DVB @@ -37,9 +37,9 @@ font: genfontfile fontfix.c fontosd.c # Dependencies: config.o : config.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h -dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h videodir.h +dvbapi.o : dvbapi.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h dvbosd.o : dvbosd.c dvbosd.h font.h tools.h -eit.o : eit.c eit.h thread.h tools.h +eit.o : eit.c config.h dvbapi.h dvbosd.h eit.h font.h thread.h tools.h videodir.h font.o : font.c font.h fontfix.c fontosd.c tools.h i18n.o : i18n.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h thread.h tools.h interface.o: interface.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h remote.h svdrp.h thread.h tools.h @@ -48,7 +48,7 @@ osd.o : osd.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h os recording.o: recording.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h recording.h remote.h svdrp.h thread.h tools.h videodir.h remote.o : remote.c config.h dvbapi.h dvbosd.h eit.h font.h remote.h thread.h tools.h svdrp.o : svdrp.c config.h dvbapi.h dvbosd.h eit.h font.h interface.h remote.h svdrp.h thread.h tools.h -thread.o : thread.c thread.h +thread.o : thread.c thread.h tools.h tools.o : tools.c tools.h vdr.o : vdr.c config.h dvbapi.h dvbosd.h eit.h font.h i18n.h interface.h menu.h osd.h recording.h remote.h svdrp.h thread.h tools.h videodir.h videodir.o : videodir.c tools.h videodir.h @@ -27,7 +27,7 @@ driver software (of course, the hardware still has to be bought). The on screen menu system is simple, but shall provide all the possibilites necessary to perform timer controlled recording, -file management and, maybe, even "on disk editing". The menus +file management and even "on disk editing". The menus of commercial set-top-boxes usually are a lot more fancy than the ones in this system, but here we have the full source code and can modify the menus in whatever way desired. @@ -3,8 +3,5 @@ TODO list for the Video Disk Recorder project * Implement simultaneous record/replay with a single DVB card once the card driver/firmware allows this. -* Implement "on-disk editing" to allow "cutting out" of certain - scenes in order to archive them (or, reversely, cut out - commercial breaks). * Implement channel scanning. * Implement remaining commands in SVDRP. diff --git a/channels.conf b/channels.conf index ce30919..43e69cd 100644 --- a/channels.conf +++ b/channels.conf @@ -1,147 +1,148 @@ -RTL:12188:h:1:27500:163:104:0:12003 -Sat.1:12480:v:1:27500:1791:1792:0:46 -Pro-7:12480:v:1:27500:255:256:0:898 -RTL2:12188:h:1:27500:166:128:0:12020 -ARD:11837:h:1:27500:101:102:0:28106 -BR3:11837:h:1:27500:201:202:0:28107 -Hessen-3:11837:h:1:27500:301:302:0:28108 -N3:12110:h:1:27500:2401:2402:0:28224 -SR3:11837:h:1:27500:501:502:0:28110 -WDR:11837:h:1:27500:601:602:0:28111 -BR-alpha:11837:h:1:27500:701:702:0:28112 -SWR BW:11837:h:1:27500:801:802:0:28113 -Phoenix:11837:h:1:27500:901:902:0:28114 -ZDF:11954:h:1:27500:110:120:0:28006 -3sat:11954:h:1:27500:210:220:0:28007 -KiKa:11954:h:1:27500:310:320:0:28008 -arte:11836:h:1:27500:401:402:0:28109 -ORF Sat:11954:h:1:27500:506:507:0:28010 -ZDF.info:11954:h:1:27500:610:620:0:28011 -CNN:12168:v:1:27500:165:100:0:28512 -Super RTL:12188:h:1:27500:165:120:0:12040 -VOX:12188:h:1:27500:167:136:0:12060 -DW TV:12363:v:1:27500:305:306:0:8905 -Kabel 1:12480:v:1:27500:511:512:0:899 -tm3:12480:v:1:27500:767:768:0:897 -DSF:12480:v:1:27500:1023:1024:0:900 -HOT:12480:v:1:27500:1279:1280:0:40 -Bloomberg TV Germany:12551:v:1:22000:162:99:0:12160 -BLOOMBERG TV:11817:v:1:27500:163:92:0:8004 -Bloomberg:12168:v:1:27500:167:112:0:12721 -Sky News:12552:v:1:22000:305:306:0:3995 -KinderNet:12574:h:1:22000:163:92:0:5020 -Alice:12610:v:1:22000:162:96:0:12200 -n-tv:12669:v:1:22000:162:96:0:12730 -Grand Tourisme:12670:v:1:22000:289:290:0:17300 -TW1:12692:h:1:22000:166:167:0:13013 -Eurosport:11954:h:1:27500:410:420:0:28009 -EinsExtra:12110:H:1:27500:101:102:0:28201 -EinsFestival:12110:H:1:27500:201:202:0:28202 -EinsMuXx:12110:H:1:27500:301:302:0:28203 -ZDF Theaterkanal:11954:H:1:27500:1110:1120:0:28016 -ZDF.doku:11954:H:1:27500:660:670:0:28014 -MDR:12110:h:1:27500:401:402:0:28204 -NICK-PARAMOUNT:12246:v:1:27500:167:108:0:29312 -ORB:12110:h:1:27500:501:502:0:28205 -B1:12110:h:1:27500:601:602:0:28206 -ARD Online-Kanal:12722:h:1:22000:8191:701:0:0 +RTL:12188:h:0:27500:163:104:0:12003 +Sat.1:12480:v:0:27500:1791:1792:0:46 +Pro-7:12480:v:0:27500:255:256:0:898 +RTL2:12188:h:0:27500:166:128:0:12020 +ARD:11837:h:0:27500:101:102:0:28106 +BR3:11837:h:0:27500:201:202:0:28107 +Hessen-3:11837:h:0:27500:301:302:0:28108 +N3:12110:h:0:27500:2401:2402:0:28224 +SR3:11837:h:0:27500:501:502:0:28110 +WDR:11837:h:0:27500:601:602:0:28111 +BR-alpha:11837:h:0:27500:701:702:0:28112 +SWR BW:11837:h:0:27500:801:802:0:28113 +Phoenix:11837:h:0:27500:901:902:0:28114 +ZDF:11954:h:0:27500:110:120:0:28006 +3sat:11954:h:0:27500:210:220:0:28007 +KiKa:11954:h:0:27500:310:320:0:28008 +arte:11836:h:0:27500:401:402:0:28109 +ORF Sat:11954:h:0:27500:506:507:0:28010 +ZDF.info:11954:h:0:27500:610:620:0:28011 +CNN:12168:v:0:27500:165:100:0:28512 +Super RTL:12188:h:0:27500:165:120:0:12040 +VOX:12188:h:0:27500:167:136:0:12060 +DW TV:12363:v:0:27500:305:306:0:8905 +Kabel 1:12480:v:0:27500:511:512:0:899 +tm3:12480:v:0:27500:767:768:0:897 +DSF:12480:v:0:27500:1023:1024:0:900 +HOT:12480:v:0:27500:1279:1280:0:40 +Bloomberg TV Germany:12551:v:0:22000:162:99:0:12160 +BLOOMBERG TV:11817:v:0:27500:163:92:0:8004 +Bloomberg:12168:v:0:27500:167:112:0:12721 +Sky News:12552:v:0:22000:305:306:0:3995 +KinderNet:12574:h:0:22000:163:92:0:5020 +Alice:12610:v:0:22000:162:96:0:12200 +n-tv:12669:v:0:22000:162:96:0:12730 +Grand Tourisme:12670:v:0:22000:289:290:0:17300 +TW1:12692:h:0:22000:166:167:0:13013 +Eurosport:11954:h:0:27500:410:420:0:28009 +EinsExtra:12110:h:0:27500:101:102:0:28201 +EinsFestival:12110:h:0:27500:201:202:0:28202 +EinsMuXx:12110:h:0:27500:301:302:0:28203 +ZDF Theaterkanal:11954:h:0:27500:1110:1120:0:28016 +ZDF.doku:11954:h:0:27500:660:670:0:28014 +MDR:12110:h:0:27500:401:402:0:28204 +NICK-PARAMOUNT:12246:v:0:27500:167:108:0:29312 +ORB:12110:h:0:27500:501:502:0:28205 +B1:12110:h:0:27500:601:602:0:28206 +ARD Online-Kanal:12722:h:0:22000:8191:701:0:0 :Premiere World -Premiere World Promo:11798:h:1:27500:255:256:0:8 -Premiere:11798:h:1:27500:511:512:2:10 -Star Kino:11798:h:1:27500:767:768:2:9 -Cine Action:11798:h:1:27500:1023:1024:2:20 -Cine Comedy:11798:h:1:27500:1279:1280:2:29 -Sci Fantasy:11798:h:1:27500:1535:1536:2:41 -Romantic Movies:11798:h:1:27500:1791:1792:2:11 -Studio Universal:11798:h:1:27500:2047:2048:2:21 -13th Street:11797:h:1:27500:2303:2304:2:43 -Junior:12031:h:1:27500:255:256:2:19 -K-Toon:12032:h:1:27500:511:512:2:12 -Disney Channel:12031:h:1:27500:767:768:2:15 -Fox Kids:11798:h:1:27500:255:256:2:0 -Sunset:12031:h:1:27500:1023:1024:2:16 -Comedy:12031:h:1:27500:1279:1280:2:28 -Planet:12031:h:1:27500:2047:2048:2:13 -Discovery Channel:12031:h:1:27500:1791:1792:2:14 -Krimi&Co:12031:h:1:27500:1535:1536:2:23 -Filmpalast:12090:v:1:27500:255:256:2:36 -Heimatkanal:11758:h:1:27500:2815:2816:2:517 -Goldstar:11758:h:1:27500:3839:3840:2:518 -Classica:12090:v:1:27500:767:768:2:34 -Seasons:12090:v:1:27500:511:512:2:33 -Blue Channel:11758:h:1:27500:2559:2560:2:516 -Feed (F1 Boxengasse):11720:h:1:27500:2559:2560:2:242 -Feed (F1 Data):11720:h:1:27500:3071:3072:2:244 -Feed (F1 Multi):11720:h:1:27500:2815:2816:2:243 -Feed (F1 On Board):11720:h:1:27500:2303:2304:2:241 -Feed (F1 Verfolger):11720:h:1:27500:2047:2048:2:240 +Premiere World Promo:11798:h:0:27500:255:256:0:8 +Premiere:11798:h:0:27500:511:512:2:10 +Star Kino:11798:h:0:27500:767:768:2:9 +Cine Action:11798:h:0:27500:1023:1024:2:20 +Cine Comedy:11798:h:0:27500:1279:1280:2:29 +Sci Fantasy:11798:h:0:27500:1535:1536:2:41 +Romantic Movies:11798:h:0:27500:1791:1792:2:11 +Studio Universal:11798:h:0:27500:2047:2048:2:21 +13th Street:11797:h:0:27500:2303:2304:2:43 +Junior:12031:h:0:27500:255:256:2:19 +K-Toon:12032:h:0:27500:511:512:2:12 +Disney Channel:12031:h:0:27500:767:768:2:15 +Fox Kids:11798:h:0:27500:255:256:2:0 +Sunset:12031:h:0:27500:1023:1024:2:16 +Comedy:12031:h:0:27500:1279:1280:2:28 +Planet:12031:h:0:27500:2047:2048:2:13 +Discovery Channel:12031:h:0:27500:1791:1792:2:14 +Krimi&Co:12031:h:0:27500:1535:1536:2:23 +Filmpalast:12090:v:0:27500:255:256:2:36 +Heimatkanal:11758:h:0:27500:2815:2816:2:517 +Goldstar:11758:h:0:27500:3839:3840:2:518 +Classica:12090:v:0:27500:767:768:2:34 +Seasons:12090:v:0:27500:511:512:2:33 +Blue Channel:11758:h:0:27500:2559:2560:2:516 +Feed (F1 Boxengasse):11720:h:0:27500:2559:2560:2:242 +Feed (F1 Data):11720:h:0:27500:3071:3072:2:244 +Feed (F1 Multi):11720:h:0:27500:2815:2816:2:243 +Feed (F1 On Board):11720:h:0:27500:2303:2304:2:241 +Feed (F1 Verfolger):11720:h:0:27500:2047:2048:2:240 : -TV Niepokalanow:11876:h:1:27500:305:321:0:20601 -Mosaico:11934:v:1:27500:165:100:0:29010 -Andalucia TV:11934:v:1:27500:166:104:0:29011 -TVC Internacional:11934:v:1:27500:167:108:0:0 -Nasza TV:11992:h:1:27500:165:98:0:0 -WishLine test:12012:v:1:27500:163:90:0:0 -Pro 7 Austria:12051:v:1:27500:161:84:0:0 -Kabel 1 Schweiz:12051:v:1:27500:162:163:0:0 -Kabel 1 Austria:12051:v:1:27500:166:167:0:0 -Pro 7 Schweiz:12051:v:1:27500:289:290:0:0 -Kiosque:12129:v:1:27500:160:80:0:0 -KTO:12129:v:1:27500:170:120:0:0 -TCM:12168:v:1:27500:160:80:0:0 -Cartoon Network France & Spain:12168:v:1:27500:161:84:0:0 -TVBS Europe:12168:v:1:27500:162:88:0:0 -TVBS Europe:12168:v:1:27500:162:89:0:0 -Travel:12168:v:1:27500:163:92:0:0 -TCM Espania:12168:v:1:27500:164:96:0:0 -MTV Spain:12168:v:1:27500:167:112:0:0 -TCM France:12168:v:1:27500:169:64:0:0 -RTL2 CH:12188:h:1:27500:164:112:0:0 -La Cinquieme:12207:v:1:27500:160:80:0:0 -ARTE:12207:v:1:27500:165:100:0:0 -Post Filial TV:12226:h:1:27500:255:256:0:0 -Canal Canaris:12246:v:1:27500:160:80:0:0 -Canal Canaris:12246:v:1:27500:160:81:0:0 -Canal Canaris:12246:v:1:27500:160:82:0:0 -Canal Canaris:12246:v:1:27500:160:83:0:0 -AB Sat Passion promo:12266:h:1:27500:160:80:0:0 -AB Channel 1:12266:h:1:27500:161:84:0:0 -Taquilla 0:12285:v:1:27500:165:100:0:0 -CSAT:12324:v:1:27500:160:80:0:0 -Mosaique:12324:v:1:27500:162:88:0:0 -Mosaique 2:12324:v:1:27500:163:92:0:0 -Mosaique 3:12324:v:1:27500:164:96:0:0 -Le Sesame C+:12324:v:1:27500:165:1965:0:0 -FEED:12344:h:1:27500:163:92:0:0 -RTM 1:12363:v:1:27500:162:96:0:0 -ESC 1:12363:v:1:27500:163:104:0:0 -TV5 Europe:12363:v:1:27500:164:112:0:0 -TV7 Tunisia:12363:v:1:27500:166:128:0:0 -ARTE:12363:v:1:27500:167:137:0:0 -RAI Uno:12363:v:1:27500:289:290:0:8904 -RTP International:12363:v:1:27500:300:301:0:0 -Fashion TV:12402:v:1:27500:163:92:0:0 -VideoService:12422:h:1:27500:255:256:0:0 -Beta Research promo:12422:h:1:27500:1023:1024:0:0 -Canal Canarias:12441:v:1:27500:160:80:0:0 -TVC International:12441:v:1:27500:512:660:0:0 -Fitur:12441:v:1:27500:514:662:0:0 -Astra Info 1:12552:v:1:22000:164:112:0:0 -Astra Info 2:12552:v:1:22000:165:120:0:0 -Astra Vision 1:12552:v:1:22000:168:144:0:0 -Astra Vision 1:12552:v:1:22000:168:145:0:0 -Astra Vision 1:12552:v:1:22000:168:146:0:0 -Astra Vision 1:12552:v:1:22000:168:147:0:0 -Astra Vision 1:12552:v:1:22000:168:148:0:0 -Astra Vision 1:12552:v:1:22000:168:149:0:0 -Astra Vision 1:12552:v:1:22000:168:150:0:0 -RTL Tele Letzebuerg:12552:v:1:22000:168:144:0:0 -Astra Mosaic:12552:v:1:22000:175:176:0:0 -MHP test:12604:h:1:22000:5632:8191:0:0 -VERONICA:12574:h:1:22000:161:84:0:5010 -VH1 Classic:12699:v:1:22000:3071:3072:0:28647 -VH-1 Germany:12699:v:1:22000:3081:3082:0:28648 -Via 1 - Schöner Reisen:12148:h:1:27500:511:512:0:44 -Video Italia:12610:v:1:22000:121:122:0:12220 -AC 3 promo:12670:v:1:22000:308:256:0:0 -ORF/ZDF:12699:h:1:22000:506:507:0:13012 +TV Niepokalanow:11876:h:0:27500:305:321:0:20601 +Mosaico:11934:v:0:27500:165:100:0:29010 +Andalucia TV:11934:v:0:27500:166:104:0:29011 +TVC Internacional:11934:v:0:27500:167:108:0:0 +Nasza TV:11992:h:0:27500:165:98:0:0 +WishLine test:12012:v:0:27500:163:90:0:0 +Pro 7 Austria:12051:v:0:27500:161:84:0:0 +Kabel 1 Schweiz:12051:v:0:27500:162:163:0:0 +Kabel 1 Austria:12051:v:0:27500:166:167:0:0 +Pro 7 Schweiz:12051:v:0:27500:289:290:0:0 +Kiosque:12129:v:0:27500:160:80:0:0 +KTO:12129:v:0:27500:170:120:0:0 +TCM:12168:v:0:27500:160:80:0:0 +Cartoon Network France & Spain:12168:v:0:27500:161:84:0:0 +TVBS Europe:12168:v:0:27500:162:88:0:0 +TVBS Europe:12168:v:0:27500:162:89:0:0 +Travel:12168:v:0:27500:163:92:0:0 +TCM Espania:12168:v:0:27500:164:96:0:0 +MTV Spain:12168:v:0:27500:167:112:0:0 +TCM France:12168:v:0:27500:169:64:0:0 +RTL2 CH:12188:h:0:27500:164:112:0:0 +La Cinquieme:12207:v:0:27500:160:80:0:0 +ARTE:12207:v:0:27500:165:100:0:0 +Post Filial TV:12226:h:0:27500:255:256:0:0 +Canal Canaris:12246:v:0:27500:160:80:0:0 +Canal Canaris:12246:v:0:27500:160:81:0:0 +Canal Canaris:12246:v:0:27500:160:82:0:0 +Canal Canaris:12246:v:0:27500:160:83:0:0 +AB Sat Passion promo:12266:h:0:27500:160:80:0:0 +AB Channel 1:12266:h:0:27500:161:84:0:0 +Taquilla 0:12285:v:0:27500:165:100:0:0 +CSAT:12324:v:0:27500:160:80:0:0 +Mosaique:12324:v:0:27500:162:88:0:0 +Mosaique 2:12324:v:0:27500:163:92:0:0 +Mosaique 3:12324:v:0:27500:164:96:0:0 +Le Sesame C+:12324:v:0:27500:165:1965:0:0 +FEED:12344:h:0:27500:163:92:0:0 +RTM 1:12363:v:0:27500:162:96:0:0 +ESC 1:12363:v:0:27500:163:104:0:0 +TV5 Europe:12363:v:0:27500:164:112:0:0 +TV7 Tunisia:12363:v:0:27500:166:128:0:0 +ARTE:12363:v:0:27500:167:137:0:0 +RAI Uno:12363:v:0:27500:289:290:0:8904 +RTP International:12363:v:0:27500:300:301:0:0 +Fashion TV:12402:v:0:27500:163:92:0:0 +VideoService:12422:h:0:27500:255:256:0:0 +Beta Research promo:12422:h:0:27500:1023:1024:0:0 +Canal Canarias:12441:v:0:27500:160:80:0:0 +TVC International:12441:v:0:27500:512:660:0:0 +Fitur:12441:v:0:27500:514:662:0:0 +Astra Info 1:12552:v:0:22000:164:112:0:0 +Astra Info 2:12552:v:0:22000:165:120:0:0 +Astra Vision 1:12552:v:0:22000:168:144:0:0 +Astra Vision 1:12552:v:0:22000:168:145:0:0 +Astra Vision 1:12552:v:0:22000:168:146:0:0 +Astra Vision 1:12552:v:0:22000:168:147:0:0 +Astra Vision 1:12552:v:0:22000:168:148:0:0 +Astra Vision 1:12552:v:0:22000:168:149:0:0 +Astra Vision 1:12552:v:0:22000:168:150:0:0 +RTL Tele Letzebuerg:12552:v:0:22000:168:144:0:0 +Astra Mosaic:12552:v:0:22000:175:176:0:0 +MHP test:12604:h:0:22000:5632:8191:0:0 +VERONICA:12574:h:0:22000:161:84:0:5010 +VH1 Classic:12699:v:0:22000:3071:3072:0:28647 +VH-1 Germany:12699:v:0:22000:3081:3082:0:28648 +Via 1 - Schöner Reisen:12148:h:0:27500:511:512:0:44 +Video Italia:12610:v:0:22000:121:122:0:12220 +AC 3 promo:12670:v:0:22000:308:256:0:0 +ORF/ZDF:12699:h:0:22000:506:507:0:13012 +VIVA:12670:v:0:22000:309:310:0:12732 @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.34 2000/11/18 13:26:36 kls Exp $ + * $Id: config.c 1.39 2001/01/14 15:29:15 kls Exp $ */ #include "config.h" @@ -119,10 +119,9 @@ bool cKeys::Load(const char *FileName) bool cKeys::Save(void) { - //TODO make backup copies??? bool result = true; - FILE *f = fopen(fileName, "w"); - if (f) { + cSafeFile f(fileName); + if (f.Open()) { if (fprintf(f, "Code\t%c\nAddress\t%04X\n", code, address) > 0) { for (tKey *k = keys; k->type != kNone; k++) { if (fprintf(f, "%s\t%08X\n", k->name, k->code) <= 0) { @@ -133,7 +132,7 @@ bool cKeys::Save(void) } else result = false; - fclose(f); + f.Close(); } else result = false; @@ -196,7 +195,7 @@ cChannel::cChannel(const cChannel *Channel) strcpy(name, Channel ? Channel->name : "Pro7"); frequency = Channel ? Channel->frequency : 12480; polarization = Channel ? Channel->polarization : 'v'; - diseqc = Channel ? Channel->diseqc : 1; + diseqc = Channel ? Channel->diseqc : 0; srate = Channel ? Channel->srate : 27500; vpid = Channel ? Channel->vpid : 255; apid = Channel ? Channel->apid : 256; @@ -435,9 +434,11 @@ bool cTimer::Parse(const char *s) //XXX to hear about that! char *s2 = NULL; int l2 = strlen(s); - if (s[l2 - 2] == ':') { // note that 's' has a trailing '\n' - s2 = (char *)malloc(l2 + 2); - strcat(strn0cpy(s2, s, l2), " \n"); + while (l2 > 0 && isspace(s[l2 - 1])) + l2--; + if (s[l2 - 1] == ':') { + s2 = (char *)malloc(l2 + 3); + strcat(strn0cpy(s2, s, l2 + 1), " \n"); s = s2; } if (8 <= sscanf(s, "%d:%d:%a[^:]:%d:%d:%d:%d:%a[^:\n]:%a[^\n]", &active, &channel, &buffer1, &start, &stop, &priority, &lifetime, &buffer2, &summary)) { @@ -723,6 +724,7 @@ cSetup::cSetup(void) MarginStart = 2; MarginStop = 10; EPGScanTimeout = 5; + CurrentChannel = -1; } bool cSetup::Parse(char *s) @@ -742,6 +744,7 @@ bool cSetup::Parse(char *s) else if (!strcasecmp(Name, "MarginStart")) MarginStart = atoi(Value); else if (!strcasecmp(Name, "MarginStop")) MarginStop = atoi(Value); else if (!strcasecmp(Name, "EPGScanTimeout")) EPGScanTimeout = atoi(Value); + else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value); else return false; return true; @@ -780,8 +783,8 @@ bool cSetup::Save(const char *FileName) if (!FileName) FileName = fileName; if (FileName) { - FILE *f = fopen(FileName, "w"); - if (f) { + cSafeFile f(FileName); + if (f.Open()) { fprintf(f, "# VDR Setup\n"); fprintf(f, "OSDLanguage = %d\n", OSDLanguage); fprintf(f, "PrimaryDVB = %d\n", PrimaryDVB); @@ -792,8 +795,10 @@ bool cSetup::Save(const char *FileName) fprintf(f, "LnbFrequHi = %d\n", LnbFrequHi); fprintf(f, "SetSystemTime = %d\n", SetSystemTime); fprintf(f, "MarginStart = %d\n", MarginStart); + fprintf(f, "MarginStop = %d\n", MarginStop); fprintf(f, "EPGScanTimeout = %d\n", EPGScanTimeout); - fclose(f); + fprintf(f, "CurrentChannel = %d\n", CurrentChannel); + f.Close(); isyslog(LOG_INFO, "saved setup to %s", FileName); return true; } @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.34 2000/11/18 13:25:53 kls Exp $ + * $Id: config.h 1.38 2001/01/14 15:29:27 kls Exp $ */ #ifndef __CONFIG_H @@ -14,11 +14,12 @@ #include <stdio.h> #include <string.h> #include <time.h> +#include <unistd.h> #include "dvbapi.h" #include "eit.h" #include "tools.h" -#define VDRVERSION "0.68" +#define VDRVERSION "0.70" #define MaxBuffer 10000 @@ -42,6 +43,15 @@ enum eKeys { // "Up" and "Down" must be the first two keys! k_Flags = k_Repeat | k_Release, }; +// This is in preparation for having more key codes: +#define kMarkToggle k0 +#define kMarkMoveBack k4 +#define kMarkMoveForward k6 +#define kMarkJumpBack k7 +#define kMarkJumpForward k9 +#define kEditCut k2 +#define kEditTest k8 + #define RAWKEY(k) ((k) & ~k_Flags) #define ISRAWKEY(k) ((k) != kNone && ((k) & k_Flags) == 0) #define NORMALKEY(k) ((k) & ~k_Repeat) @@ -157,43 +167,45 @@ private: cList<T>::Clear(); } public: + cConfig(void) { fileName = NULL; } + virtual ~cConfig() { delete fileName; } virtual bool Load(const char *FileName) { - isyslog(LOG_INFO, "loading %s", FileName); - bool result = true; Clear(); fileName = strdup(FileName); - FILE *f = fopen(fileName, "r"); - if (f) { - int line = 0; - char buffer[MaxBuffer]; - while (fgets(buffer, sizeof(buffer), f) > 0) { - line++; - T *l = new T; - if (l->Parse(buffer)) - Add(l); - else { - esyslog(LOG_ERR, "error in %s, line %d\n", fileName, line); - delete l; - result = false; - break; + bool result = false; + if (access(FileName, F_OK) == 0) { + isyslog(LOG_INFO, "loading %s", FileName); + FILE *f = fopen(fileName, "r"); + if (f) { + int line = 0; + char buffer[MaxBuffer]; + result = true; + while (fgets(buffer, sizeof(buffer), f) > 0) { + line++; + T *l = new T; + if (l->Parse(buffer)) + Add(l); + else { + esyslog(LOG_ERR, "error in %s, line %d\n", fileName, line); + delete l; + result = false; + break; + } } - } - fclose(f); - } - else { - LOG_ERROR_STR(fileName); - result = false; + fclose(f); + } + else + LOG_ERROR_STR(fileName); } return result; } bool Save(void) { - //TODO make backup copies??? bool result = true; T *l = (T *)First(); - FILE *f = fopen(fileName, "w"); - if (f) { + cSafeFile f(fileName); + if (f.Open()) { while (l) { if (!l->Save(f)) { result = false; @@ -201,12 +213,10 @@ public: } l = (T *)l->Next(); } - fclose(f); + f.Close(); } - else { - LOG_ERROR_STR(fileName); + else result = false; - } return result; } }; @@ -258,6 +268,7 @@ public: int SetSystemTime; int MarginStart, MarginStop; int EPGScanTimeout; + int CurrentChannel; cSetup(void); bool Load(const char *FileName); bool Save(const char *FileName = NULL); diff --git a/dvb.c.071.diff b/dvb.c.071.diff deleted file mode 100644 index ac7ba3a..0000000 --- a/dvb.c.071.diff +++ /dev/null @@ -1,100 +0,0 @@ ---- dvb.c.001 Sun Sep 17 22:02:37 2000 -+++ dvb.c Tue Oct 3 12:11:46 2000 -@@ -1143,6 +1143,8 @@ - { - int bpp; - int i; -+ int d, delta; //XXX kls: additional variables for data compression -+ u8 c; //XXX kls: additional variables for data compression - DECLARE_WAITQUEUE(wait, current); - - if (dvb->bmp_state==BMP_LOADING) { -@@ -1160,27 +1162,38 @@ - if (dvb->bmp_state==BMP_LOADING) - return -1; - dvb->bmp_state=BMP_LOADING; -- if (format==BITMAP8) bpp=8; -- else if (format==BITMAP4) bpp=4; -- else if (format==BITMAP2) bpp=2; -- else if (format==BITMAP1) bpp=1; -+ if (format==BITMAP8) { bpp=8; delta = 1; } //XXX kls: initialize 'delta', too -+ else if (format==BITMAP4) { bpp=4; delta = 2; } -+ else if (format==BITMAP2) { bpp=2; delta = 4; } -+ else if (format==BITMAP1) { bpp=1; delta = 8; } - else { - dvb->bmp_state=BMP_NONE; - return -1; - } -- dvb->bmplen= (dx*dy*bpp)/8; -+ dvb->bmplen= ((dx*dy*bpp+7)&~7)/8; //XXX kls: need to round up to include partial bytes - dvb->bmpp=0; - if (dvb->bmplen>32768) { - dvb->bmp_state=BMP_NONE; - return -1; - } - for (i=0; i<dy; i++) { -- if (copy_from_user(dvb->bmpbuf+1024+i*dx, data+i*inc, (dx*bpp)/8)) { -+ if (copy_from_user(dvb->bmpbuf+1024+i*dx, data+i*inc, dx)) { //XXX kls: incoming data is "1 byte per pixel" - dvb->bmp_state=BMP_NONE; - return -1; - } - - } -+ // XXX kls: Incoming data is always "one byte per pixel", so we need to compress -+ // the data in case we have a lower resolution than 8 bpp: -+ if (format != BITMAP8) { -+ for (i=0; i<dx*dy/delta; i++) { -+ c = ((u8 *)dvb->bmpbuf)[1024+i*delta+delta-1]; -+ for (d=delta-2; d>=0; d--) { -+ c |= (((u8 *)dvb->bmpbuf)[1024+i*delta+d] << ((delta-d-1)*bpp)); -+ ((u8 *)dvb->bmpbuf)[1024+i] = c; -+ } -+ } -+ } - dvb->bmplen+=1024; - return outcom(dvb, COMTYPE_OSD, LoadBmp, 3, format, dx, dy); - } -@@ -1256,24 +1269,25 @@ - int i; - - w=x1-x0+1; h=y1-y0+1; -- if (inc>0) -- w=inc; -- if (w>720 || h>576) -+ if (inc<=0) -+ inc=w; //XXX kls: see dvb_4l.h: "inc<=0 uses blockwidth as linewidth" -+ if (w<=0 || w>720 || h<=0 || h>576) //XXX kls: checking lower bounds, too - return -1; -- bpp=8; //dvb->osdbpp[dvb->osdwin]; -- bpl=w*bpp/8; -+ bpp=dvb->osdbpp[dvb->osdwin]+1; //XXX kls: 'bpp' needs to be taken from the window -+ bpl=((w*bpp+7)&~7)/8; //XXX kls: need to round up to include partial bytes - size=h*bpl; -- lpb=(64*1024)/bpl; -+ lpb=(32*1024)/bpl; //XXX kls: apparently 32K is the maximum possible value - bnum=size/(lpb*bpl); - brest=size-bnum*lpb*bpl; - - for (i=0; i<bnum; i++) { -- LoadBitmap(dvb, BITMAP8, w, lpb, inc, data); -+ LoadBitmap(dvb, bpp2bit[dvb->osdbpp[dvb->osdwin]], w, lpb, inc, data); //XXX kls: need to take the format from the actual window - BlitBitmap(dvb, dvb->osdwin, x0, y0+i*lpb, 1); -- data+=bpl; -+ data+=lpb*inc; //XXX kls: incrementing must be done in "one byte per pixel" -+ ddelay(3); //XXX kls: without this the block is sometimes not fully displayed - firmware bug? - } - if (brest) { -- LoadBitmap(dvb, BITMAP8, w, brest/bpl, inc, data); -+ LoadBitmap(dvb, bpp2bit[dvb->osdbpp[dvb->osdwin]], w, brest/bpl, inc, data); //XXX kls: need to take the format from the actual window - BlitBitmap(dvb, dvb->osdwin, x0, y0+bnum*lpb, 1); - } - ReleaseBitmap(dvb); -@@ -6141,7 +6155,7 @@ - init_waitqueue_head(&dvb->bmpq); - spin_lock_init (&(dvb->bmplock)); - dvb->bmpp=dvb->bmplen=0; -- dvb->bmpbuf=vmalloc(32768+1024); -+ dvb->bmpbuf=vmalloc(8*32768+1024); //XXX kls: '8*' to be prepared for the maximum possible incoming data at 1 bpp - - init_waitqueue_head(&dvb->debiq); - dvb->debilock=SPIN_LOCK_UNLOCKED; @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.c 1.40 2000/11/19 16:46:37 kls Exp $ + * $Id: dvbapi.c 1.50 2001/01/18 19:53:54 kls Exp $ */ #include "dvbapi.h" @@ -21,6 +21,7 @@ extern "C" { #include <unistd.h> #include "config.h" #include "interface.h" +#include "recording.h" #include "tools.h" #include "videodir.h" @@ -32,23 +33,25 @@ extern "C" { // The minimum amount of video data necessary to identify frames // (must be smaller than VIDEOBUFSIZE!): -#define MINVIDEODATA (20*1024) // just a safe guess (max. size of any AV_PES block, plus some safety) +#define MINVIDEODATA (256*1024) // just a safe guess (max. size of any frame block, plus some safety) // The maximum time the buffer is allowed to write data to disk when recording: #define MAXRECORDWRITETIME 50 // ms -#define AV_PES_HEADER_LEN 8 - -// AV_PES block types: -#define AV_PES_VIDEO 1 -#define AV_PES_AUDIO 2 - // Picture types: #define NO_PICTURE 0 #define I_FRAME 1 #define P_FRAME 2 #define B_FRAME 3 +// Start codes: +#define SC_PICTURE 0x00 // "picture header" +#define SC_SEQU 0xB3 // "sequence header" +#define SC_PHEAD 0xBA // "pack header" +#define SC_SHEAD 0xBB // "system header" +#define SC_AUDIO 0xC0 +#define SC_VIDEO 0xE0 + #define FRAMESPERSEC 25 // The maximum file size is limited by the range that can be covered @@ -73,6 +76,35 @@ extern "C" { typedef unsigned char uchar; +static void SetPlayMode(int VideoDev, int Mode) +{ + if (VideoDev >= 0) { + struct video_play_mode pmode; + pmode.mode = Mode; + ioctl(VideoDev, VIDIOCSPLAYMODE, &pmode); + } +} + +const char *IndexToHMSF(int Index, bool WithFrame) +{ + static char buffer[16]; + int f = (Index % FRAMESPERSEC) + 1; + int s = (Index / FRAMESPERSEC); + int m = s / 60 % 60; + int h = s / 3600; + s %= 60; + snprintf(buffer, sizeof(buffer), WithFrame ? "%d:%02d:%02d.%02d" : "%d:%02d:%02d", h, m, s, f); + return buffer; +} + +int HMSFToIndex(const char *HMSF) +{ + int h, m, s, f = 0; + if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f)) + return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1; + return 0; +} + // --- cResumeFile ------------------------------------------------------------ cResumeFile::cResumeFile(const char *FileName) @@ -135,16 +167,15 @@ private: cResumeFile resumeFile; bool CatchUp(void); public: - cIndexFile(const char *FileName, bool Record = false); + cIndexFile(const char *FileName, bool Record); ~cIndexFile(); void Write(uchar PictureType, uchar FileNumber, int FileOffset); - bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL); - int GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length = NULL); + bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL); + int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL); int Get(uchar FileNumber, int FileOffset); - int Last(void) { return last; } + int Last(void) { CatchUp(); return last; } int GetResume(void) { return resumeFile.Read(); } bool StoreResume(int Index) { return resumeFile.Save(Index); } - static char *Str(int Index, bool WithFrame = false); }; cIndexFile::cIndexFile(const char *FileName, bool Record) @@ -276,7 +307,7 @@ void cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) } } -bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType) +bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length) { if (index) { CatchUp(); @@ -285,6 +316,14 @@ bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *Pictu *FileOffset = index[Index].offset; if (PictureType) *PictureType = index[Index].type; + if (Length) { + int fn = index[Index + 1].number; + int fo = index[Index + 1].offset; + if (fn == *FileNumber) + *Length = fo - *FileOffset; + else + *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly) + } return true; } } @@ -301,8 +340,14 @@ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *F Index += d; if (Index >= 0 && Index <= last - 100) { // '- 100': need to stay off the end! if (index[Index].type == I_FRAME) { - *FileNumber = index[Index].number; - *FileOffset = index[Index].offset; + if (FileNumber) + *FileNumber = index[Index].number; + else + FileNumber = &index[Index].number; + if (FileOffset) + *FileOffset = index[Index].offset; + else + FileOffset = &index[Index].offset; if (Length) { // all recordings end with a non-I_FRAME, so the following should be safe: int fn = index[Index + 1].number; @@ -339,18 +384,6 @@ int cIndexFile::Get(uchar FileNumber, int FileOffset) return -1; } -char *cIndexFile::Str(int Index, bool WithFrame) -{ - static char buffer[16]; - int f = (Index % FRAMESPERSEC) + 1; - int s = (Index / FRAMESPERSEC); - int m = s / 60 % 60; - int h = s / 3600; - s %= 60; - snprintf(buffer, sizeof(buffer), WithFrame ? "%d:%02d:%02d.%02d" : "%d:%02d:%02d", h, m, s, f); - return buffer; -} - // --- cRingBuffer ----------------------------------------------------------- /* cRingBuffer reads data from an input file, stores it in a buffer and writes @@ -378,6 +411,12 @@ protected: int Readable(void) { return (tail >= head) ? size - tail - (head ? 0 : 1) : head - tail - 1; } // keep a 1 byte gap! int Writeable(void) { return (tail >= head) ? tail - head : size - head; } int Byte(int Offset); + bool Set(int Offset, int Length, int Value); +protected: + int GetStartCode(int Offset) { return (Byte(Offset) == 0x00 && Byte(Offset + 1) == 0x00 && Byte(Offset + 2) == 0x01) ? Byte(Offset + 3) : -1; } + int GetPictureType(int Offset) { return (Byte(Offset + 5) >> 3) & 0x07; } + int FindStartCode(uchar Code, int Offset = 0); + int GetPacketLength(int Offset = 0); public: cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit = 0, int AvailLimit = 0); virtual ~cRingBuffer(); @@ -422,19 +461,36 @@ int cRingBuffer::Byte(int Offset) return -1; } -void cRingBuffer::Skip(int n) +bool cRingBuffer::Set(int Offset, int Length, int Value) { - if (head < tail) { - head += n; - if (head > tail) - head = tail; + if (buffer && Offset + Length <= Available() ) { + Offset += head; + while (Length--) { + if (Offset >= size) + Offset -= size; + buffer[Offset] = Value; + Offset++; + } + return true; } - else if (head > tail) { - head += n; - if (head >= size) - head -= size; - if (head > tail) - head = tail; + return false; +} + +void cRingBuffer::Skip(int n) +{ + if (n > 0) { + if (head < tail) { + head += n; + if (head > tail) + head = tail; + } + else if (head > tail) { + head += n; + if (head >= size) + head -= size; + if (head > tail) + head = tail; + } } } @@ -477,9 +533,11 @@ int cRingBuffer::Read(int Max) if (tail >= size) tail = 0; } - else if (r < 0 && errno != EAGAIN) { - LOG_ERROR; - return -1; + else if (r < 0) { + if (errno != EAGAIN) { + LOG_ERROR; + return -1; + } } else eof = true; @@ -533,37 +591,53 @@ int cRingBuffer::Write(int Max) return -1; } -// --- cFileBuffer ----------------------------------------------------------- +int cRingBuffer::FindStartCode(uchar Code, int Offset) +{ + // Searches for a start code (beginning at Offset) and returns the number + // of bytes from Offset to the start code. -class cFileBuffer : public cRingBuffer { -protected: - cIndexFile *index; - uchar fileNumber; + int n = Available() - Offset; + + for (int i = 0; i < n; i++) { + int c = GetStartCode(Offset + i); + if (c == Code) + return i; + if (i > 0 && c == SC_PHEAD) + break; // found another block start while looking for a different code + } + return -1; +} + +int cRingBuffer::GetPacketLength(int Offset) +{ + // Returns the entire length of the packet starting at offset. + return (Byte(Offset + 4) << 8) + Byte(Offset + 5) + 6; +} + +// --- cFileName ------------------------------------------------------------- + +class cFileName { +private: + int file; + int fileNumber; char *fileName, *pFileNumber; - bool stop; - int GetAvPesLength(void) - { - if (Byte(0) == 'A' && Byte(1) == 'V' && Byte(4) == 'U') - return (Byte(6) << 8) + Byte(7) + AV_PES_HEADER_LEN; - return 0; - } - int GetAvPesType(void) { return Byte(2); } - int GetAvPesTag(void) { return Byte(3); } - int FindAvPesBlock(void); - int GetPictureType(int Offset) { return (Byte(Offset + 5) >> 3) & 0x07; } - int FindPictureStartCode(int Length); + bool record; public: - cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0); - virtual ~cFileBuffer(); - void Stop(void) { stop = true; } + cFileName(const char *FileName, bool Record); + ~cFileName(); + const char *Name(void) { return fileName; } + int Number(void) { return fileNumber; } + int Open(void); + void Close(void); + int SetOffset(int Number, int Offset = 0); + int NextFile(void); }; -cFileBuffer::cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0) -:cRingBuffer(InFile, OutFile, Size, FreeLimit, AvailLimit) +cFileName::cFileName(const char *FileName, bool Record) { - index = NULL; + file = -1; fileNumber = 0; - stop = false; + record = Record; // Prepare the file name: fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN]; if (!fileName) { @@ -572,98 +646,165 @@ cFileBuffer::cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool R } strcpy(fileName, FileName); pFileNumber = fileName + strlen(fileName); - // Create the index file: - index = new cIndexFile(FileName, Recording); - if (!index) - esyslog(LOG_ERR, "ERROR: can't allocate index"); - // let's continue without index, so we'll at least have the recording + SetOffset(1); } -cFileBuffer::~cFileBuffer() +cFileName::~cFileName() { - delete index; + Close(); delete fileName; } -int cFileBuffer::FindAvPesBlock(void) +int cFileName::Open(void) +{ + if (file < 0) { + if (record) { + dsyslog(LOG_INFO, "recording to '%s'", fileName); + file = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_NONBLOCK); + if (file < 0) + LOG_ERROR_STR(fileName); + } + else { + if (access(fileName, R_OK) == 0) { + dsyslog(LOG_INFO, "playing '%s'", fileName); + file = open(fileName, O_RDONLY | O_NONBLOCK); + if (file < 0) + LOG_ERROR_STR(fileName); + } + else if (errno != ENOENT) + LOG_ERROR_STR(fileName); + } + } + return file; +} + +void cFileName::Close(void) { - int Skipped = 0; + if (file >= 0) { + if ((record && CloseVideoFile(file) < 0) || (!record && close(file) < 0)) + LOG_ERROR_STR(fileName); + file = -1; + } +} - while (Available() > MINVIDEODATA) { - if (GetAvPesLength()) - return Skipped; - Skip(1); - Skipped++; +int cFileName::SetOffset(int Number, int Offset) +{ + if (fileNumber != Number) + Close(); + if (0 < Number && Number <= MAXFILESPERRECORDING) { + fileNumber = Number; + sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); + if (record) { + if (access(fileName, F_OK) == 0) // file exists, let's try next suffix + return SetOffset(Number + 1); + else if (errno != ENOENT) { // something serious has happened + LOG_ERROR_STR(fileName); + return -1; + } + // found a non existing file suffix } + if (Open() >= 0) { + if (!record && Offset >= 0 && lseek(file, Offset, SEEK_SET) != Offset) { + LOG_ERROR_STR(fileName); + return -1; + } + } + return file; + } + esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); return -1; } -int cFileBuffer::FindPictureStartCode(int Length) +int cFileName::NextFile(void) { - for (int i = AV_PES_HEADER_LEN; i < Length; i++) { - if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1 && Byte(i + 3) == 0) - return i; - } - return -1; + return SetOffset(fileNumber + 1); } // --- cRecordBuffer --------------------------------------------------------- -class cRecordBuffer : public cFileBuffer { +class cRecordBuffer : public cRingBuffer, public cThread { private: + cFileName fileName; + cIndexFile *index; uchar pictureType; int fileSize; + int videoDev; int recordFile; - uchar tagAudio, tagVideo; - bool ok, synced; + bool ok, synced, stop; time_t lastDiskSpaceCheck; bool RunningLowOnDiskSpace(void); + int ScanVideoPacket(int *PictureType, int Offset); int Synchronize(void); bool NextFile(void); virtual int Write(int Max = -1); + bool WriteWithTimeout(void); +protected: + virtual void Action(void); public: cRecordBuffer(int *InFile, const char *FileName); virtual ~cRecordBuffer(); - int WriteWithTimeout(bool EndIfEmpty = false); }; cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName) -:cFileBuffer(InFile, &recordFile, FileName, true, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0) +:cRingBuffer(InFile, &recordFile, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0) +,fileName(FileName, true) { + index = NULL; pictureType = NO_PICTURE; fileSize = 0; - recordFile = -1; - tagAudio = tagVideo = 0; - ok = synced = false; + videoDev = *InFile; + recordFile = fileName.Open(); + ok = synced = stop = false; lastDiskSpaceCheck = time(NULL); - if (!fileName) - return;//XXX find a better way??? - // Find the highest existing file suffix: - for (;;) { - sprintf(pFileNumber, RECORDFILESUFFIX, ++fileNumber); - if (access(fileName, F_OK) < 0) { - if (errno == ENOENT) - break; // found a non existing file suffix - LOG_ERROR; - return; - } - } + if (!fileName.Name()) + return; + // Create the index file: + index = new cIndexFile(FileName, true); + if (!index) + esyslog(LOG_ERR, "ERROR: can't allocate index"); + // let's continue without index, so we'll at least have the recording ok = true; - //XXX hack to make the video device go into 'recording' mode: - char dummy; - read(*InFile, &dummy, sizeof(dummy)); + Start(); } cRecordBuffer::~cRecordBuffer() { - if (recordFile >= 0) - CloseVideoFile(recordFile); + stop = true; + Cancel(3); + delete index; +} + +void cRecordBuffer::Action(void) +{ + dsyslog(LOG_INFO, "recording thread started (pid=%d)", getpid()); + + time_t t = time(NULL); + for (;;) { + usleep(1); // this keeps the CPU load low + + LOCK_THREAD; + + int r = Read(); + if (r >= 0) { + if (r > 0) + t = time(NULL); + if (!WriteWithTimeout()) + break; + } + if (r < 0 || (r == 0 && time(NULL) - t > 5)) { + esyslog(LOG_ERR, "ERROR: video data stream broken"); + t = time(NULL); + } + } + SetPlayMode(videoDev, VID_PLAY_RESET); + + dsyslog(LOG_INFO, "end recording thread"); } bool cRecordBuffer::RunningLowOnDiskSpace(void) { if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { - uint Free = FreeDiskSpaceMB(fileName); + uint Free = FreeDiskSpaceMB(fileName.Name()); lastDiskSpaceCheck = time(NULL); if (Free < MINFREEDISKSPACE) { dsyslog(LOG_INFO, "low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE); @@ -673,109 +814,109 @@ bool cRecordBuffer::RunningLowOnDiskSpace(void) return false; } +int cRecordBuffer::ScanVideoPacket(int *PictureType, int Offset) +{ + // Scans the video packet starting at Offset and returns its length. + // If the return value is -1 the packet was not completely in the buffer. + + int Length = GetPacketLength(Offset); + if (Length <= Available()) { + for (int i = Offset; i < Offset + Length; i++) { + if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) { + switch (Byte(i + 3)) { + case SC_PICTURE: *PictureType = GetPictureType(i); + return Length; + } + } + } + *PictureType = NO_PICTURE; + return Length; + } + return -1; +} + int cRecordBuffer::Synchronize(void) { - int Length = 0; - int Skipped = 0; - int s; - - while ((s = FindAvPesBlock()) >= 0) { - pictureType = NO_PICTURE; - Skipped += s; - Length = GetAvPesLength(); - if (Length <= MINVIDEODATA) { - switch (GetAvPesType()) { - case AV_PES_VIDEO: - { - int PictureOffset = FindPictureStartCode(Length); - if (PictureOffset >= 0) { - pictureType = GetPictureType(PictureOffset); - if (pictureType < I_FRAME || pictureType > B_FRAME) - esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pictureType); - } - } - if (!synced) { - tagVideo = GetAvPesTag(); - if (pictureType == I_FRAME) { - synced = true; - Skipped = 0; - } - else { - Skip(Length - 1); - Length = 0; - } - } - else { - if (++tagVideo != GetAvPesTag()) { - esyslog(LOG_ERR, "ERROR: missed video data block #%d (next block is #%d)", tagVideo, GetAvPesTag()); - tagVideo = GetAvPesTag(); - } - } - break; - case AV_PES_AUDIO: - if (!synced) { - tagAudio = GetAvPesTag(); - Skip(Length - 1); - Length = 0; - } - else { - //XXX might get fooled the first time!!! - if (++tagAudio != GetAvPesTag()) { - esyslog(LOG_ERR, "ERROR: missed audio data block #%d (next block is #%d)", tagAudio, GetAvPesTag()); - tagAudio = GetAvPesTag(); - } - } - break; - default: // unknown data - Length = 0; // don't skip entire block - maybe we're just not in sync with AV_PES yet - if (synced) - esyslog(LOG_ERR, "ERROR: unknown data type '%d'", GetAvPesType()); - } - if (Length > 0) - break; - } - else if (synced) { - esyslog(LOG_ERR, "ERROR: block length too big (%d)", Length); - Length = 0; + // Positions to the start of a data block (skipping everything up to + // an I-frame if not synced) and returns the block length. + + int LastPackHeader = -1; + + pictureType = NO_PICTURE; + + //XXX remove this once the buffer is handled with two separate threads: + if (!synced && Free() < 100000) { + dsyslog(LOG_INFO, "unable to synchronize, dropped %d bytes", Available()); + Clear(); + return 0; + } + for (int i = 0; Available() > MINVIDEODATA && i < MINVIDEODATA; i++) { + if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) { + switch (Byte(i + 3)) { + case SC_PHEAD: LastPackHeader = i; + break; + case SC_VIDEO: { + int pt = NO_PICTURE; + int l = ScanVideoPacket(&pt, i); + if (l < 0) + return 0; // no useful data found, wait for more + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pt); + } + else if (pictureType == NO_PICTURE) { + if (!synced) { + if (LastPackHeader == 0) { + if (pt == I_FRAME) + synced = true; + } + else if (LastPackHeader > 0) { + Skip(LastPackHeader); + LastPackHeader = -1; + i = 0; + break; + } + else { // LastPackHeader < 0 + Skip(i + l); + i = 0; + break; + } + } + if (synced) + pictureType = pt; + } + else if (LastPackHeader > 0) + return LastPackHeader; + else + return i; + } + i += l - 1; // -1 to compensate for i++ in the loop! + LastPackHeader = -1; + } + break; + case SC_AUDIO: i += GetPacketLength(i) - 1; // -1 to compensate for i++ in the loop! + break; } - Skip(1); - Skipped++; - } - if (synced && Skipped) - esyslog(LOG_ERR, "ERROR: skipped %d bytes", Skipped); - return Length; + } + } + return 0; // no useful data found, wait for more } bool cRecordBuffer::NextFile(void) { if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME if (fileSize > MAXVIDEOFILESIZE || RunningLowOnDiskSpace()) { - if (CloseVideoFile(recordFile) < 0) - LOG_ERROR; - // don't return 'false', maybe we can still record into the next file - recordFile = -1; - fileNumber++; - if (fileNumber == 0) - esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); + recordFile = fileName.NextFile(); fileSize = 0; } } - if (recordFile < 0) { - sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); - dsyslog(LOG_INFO, "recording to '%s'", fileName); - recordFile = OpenVideoFile(fileName, O_RDWR | O_CREAT | O_NONBLOCK); - if (recordFile < 0) { - LOG_ERROR; - return false; - } - } - return true; + return recordFile >= 0; } int cRecordBuffer::Write(int Max) { // This function ignores the incoming 'Max'! - // It tries to write out exactly *one* block of AV_PES data. + // It tries to write out exactly *one* frame block. if (!ok) return -1; int n = Synchronize(); @@ -786,10 +927,10 @@ int cRecordBuffer::Write(int Max) } if (NextFile()) { if (index && pictureType != NO_PICTURE) - index->Write(pictureType, fileNumber, fileSize); + index->Write(pictureType, fileName.Number(), fileSize); int written = 0; for (;;) { - int w = cFileBuffer::Write(n); + int w = cRingBuffer::Write(n); if (w >= 0) { fileSize += w; written += w; @@ -806,30 +947,41 @@ int cRecordBuffer::Write(int Max) return 0; } -int cRecordBuffer::WriteWithTimeout(bool EndIfEmpty) +bool cRecordBuffer::WriteWithTimeout(void) { - int w, written = 0; int t0 = time_ms(); - while ((w = Write()) > 0 && time_ms() - t0 < MAXRECORDWRITETIME) - written += w; - return w < 0 ? w : (written == 0 && EndIfEmpty ? -1 : written); + do { + int w = Write(); + if (w < 0) + return false; + if (w == 0) + break; + } while (time_ms() - t0 < MAXRECORDWRITETIME); + return true; } // --- cReplayBuffer --------------------------------------------------------- -enum eReplayMode { rmPlay, rmFastForward, rmFastRewind, rmSlowRewind }; - -class cReplayBuffer : public cFileBuffer { +class cReplayBuffer : public cRingBuffer, public cThread { private: + enum eReplayCmd { rcNone, rcStill, rcPause, rcPlay, rcForward, rcBackward }; + enum eReplayMode { rmStill, rmPlay, rmFastForward, rmFastRewind, rmSlowRewind }; + cIndexFile *index; + cFileName fileName; int fileOffset; + int videoDev; int replayFile; eReplayMode mode; - bool skipAudio; - int lastIndex; + int lastIndex, stillIndex; int brakeCounter; - void SkipAudioBlocks(void); + eReplayCmd command; + bool active; bool NextFile(uchar FileNumber = 0, int FileOffset = -1); void Close(void); + void SetCmd(eReplayCmd Cmd) { LOCK_THREAD; command = Cmd; } + void SetTemporalReference(void); +protected: + virtual void Action(void); public: cReplayBuffer(int *OutFile, const char *FileName); virtual ~cReplayBuffer(); @@ -838,38 +990,136 @@ public: void SetMode(eReplayMode Mode); int Resume(void); bool Save(void); + void Pause(void) { SetCmd(rcPause); } + void Play(void) { SetCmd(rcPlay); } + void Forward(void) { SetCmd(rcForward); } + void Backward(void) { SetCmd(rcBackward); } + int SkipFrames(int Frames); void SkipSeconds(int Seconds); - void GetIndex(int &Current, int &Total); + void Goto(int Position, bool Still = false); + void GetIndex(int &Current, int &Total, bool SnapToIFrame = false); }; cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName) -:cFileBuffer(&replayFile, OutFile, FileName, false, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) +:cRingBuffer(&replayFile, OutFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) +,fileName(FileName, false) { + index = NULL; fileOffset = 0; - replayFile = -1; + videoDev = *OutFile; + replayFile = fileName.Open(); mode = rmPlay; brakeCounter = 0; - skipAudio = false; - lastIndex = -1; - if (!fileName) - return;//XXX find a better way??? - // All recordings start with '1': - fileNumber = 1; //TODO what if it doesn't start with '1'??? - //XXX hack to make the video device go into 'replaying' mode: - char *dummy = "AV"; // must be "AV" to make the driver go into AV_PES mode! - write(*OutFile, dummy, strlen(dummy)); + command = rcNone; + lastIndex = stillIndex = -1; + active = false; + if (!fileName.Name()) + return; + // Create the index file: + index = new cIndexFile(FileName, false); + if (!index) + esyslog(LOG_ERR, "ERROR: can't allocate index"); + // let's continue without index, so we'll at least have the recording + Start(); } cReplayBuffer::~cReplayBuffer() { + active = false; + Cancel(3); Close(); + SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + SetPlayMode(videoDev, VID_PLAY_RESET); + delete index; +} + +void cReplayBuffer::Action(void) +{ + dsyslog(LOG_INFO, "replay thread started (pid=%d)", getpid()); + + bool Paused = false; + bool FastForward = false; + bool FastRewind = false; + + int ResumeIndex = Resume(); + if (ResumeIndex >= 0) + isyslog(LOG_INFO, "resuming replay at index %d (%s)", ResumeIndex, IndexToHMSF(ResumeIndex, true)); + active = true; + while (active) { + usleep(1); // this keeps the CPU load low + + LOCK_THREAD; + + if (command != rcNone) { + switch (command) { + case rcStill: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + SetPlayMode(videoDev, VID_PLAY_NORMAL); + SetMode(rmStill); + Paused = FastForward = FastRewind = false; + break; + case rcPause: SetPlayMode(videoDev, Paused ? VID_PLAY_NORMAL : VID_PLAY_PAUSE); + Paused = !Paused; + if (FastForward || FastRewind) { + SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + Clear(); + } + FastForward = FastRewind = false; + SetMode(rmPlay); + if (!Paused) + stillIndex = -1; + break; + case rcPlay: if (FastForward || FastRewind || Paused) { + SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + SetPlayMode(videoDev, VID_PLAY_NORMAL); + FastForward = FastRewind = Paused = false; + SetMode(rmPlay); + } + stillIndex = -1; + break; + case rcForward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + Clear(); + FastForward = !FastForward; + FastRewind = false; + if (Paused) { + SetMode(rmPlay); + SetPlayMode(videoDev, FastForward ? VID_PLAY_SLOW_MOTION : VID_PLAY_PAUSE); + } + else { + SetPlayMode(videoDev, VID_PLAY_NORMAL); + SetMode(FastForward ? rmFastForward : rmPlay); + } + stillIndex = -1; + break; + case rcBackward: SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + Clear(); + FastRewind = !FastRewind; + FastForward = false; + if (Paused) { + SetMode(FastRewind ? rmSlowRewind : rmPlay); + SetPlayMode(videoDev, FastRewind ? VID_PLAY_NORMAL : VID_PLAY_PAUSE); + } + else { + SetPlayMode(videoDev, VID_PLAY_NORMAL); + SetMode(FastRewind ? rmFastRewind : rmPlay); + } + stillIndex = -1; + break; + default: break; + } + command = rcNone; + } + if (Read() < 0 || Write() < 0) + break; + } + Save(); + + dsyslog(LOG_INFO, "end replaying thread"); } void cReplayBuffer::Close(void) { if (replayFile >= 0) { - if (close(replayFile) < 0) - LOG_ERROR; + fileName.Close(); replayFile = -1; fileOffset = 0; } @@ -878,7 +1128,6 @@ void cReplayBuffer::Close(void) void cReplayBuffer::SetMode(eReplayMode Mode) { mode = Mode; - skipAudio = Mode != rmPlay; brakeCounter = 0; if (mode != rmPlay) Clear(); @@ -901,14 +1150,11 @@ int cReplayBuffer::Resume(void) bool cReplayBuffer::Save(void) { if (index) { - int Index = index->Get(fileNumber, fileOffset); + int Index = index->Get(fileName.Number(), fileOffset); if (Index >= 0) { Index -= RESUMEBACKUP; - if (Index > 0) { - uchar FileNumber; - int FileOffset; - Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset); - } + if (Index > 0) + Index = index->GetNextIFrame(Index, false); else Index = 0; if (Index >= 0) @@ -918,15 +1164,39 @@ bool cReplayBuffer::Save(void) return false; } +int cReplayBuffer::SkipFrames(int Frames) +{ + if (index && Frames) { + + LOCK_THREAD; + + int Current, Total; + GetIndex(Current, Total, true); + int OldCurrent = Current; + Current = index->GetNextIFrame(Current + Frames, Frames > 0); + return Current >= 0 ? Current : OldCurrent; + } + return -1; +} + void cReplayBuffer::SkipSeconds(int Seconds) { + LOCK_THREAD; + + SetPlayMode(videoDev, VID_PLAY_PAUSE); + SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + SetPlayMode(videoDev, VID_PLAY_NORMAL); + command = rcPlay; + SetMode(rmPlay); + Clear(); + if (index && Seconds) { - int Index = index->Get(fileNumber, fileOffset); + int Index = index->Get(fileName.Number(), fileOffset); if (Index >= 0) { if (Seconds < 0) { int sec = index->Last() / FRAMESPERSEC; if (Seconds < -sec) - Seconds = - sec; + Seconds = -sec; } Index += Seconds * FRAMESPERSEC; if (Index < 0) @@ -934,103 +1204,120 @@ void cReplayBuffer::SkipSeconds(int Seconds) uchar FileNumber; int FileOffset; if (index->GetNextIFrame(Index, false, &FileNumber, &FileOffset) >= 0) - if ((Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset)) >= 0) NextFile(FileNumber, FileOffset); } } } -void cReplayBuffer::GetIndex(int &Current, int &Total) +void cReplayBuffer::Goto(int Index, bool Still) { - if (index) { - Current = index->Get(fileNumber, fileOffset); - Total = index->Last(); - } - else - Current = Total = -1; + LOCK_THREAD; + + if (Still) + command = rcStill; + if (++Index <= 0) + Index = 1; // not '0', to allow GetNextIFrame() below to work! + uchar FileNumber; + int FileOffset; + if ((stillIndex = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset)) >= 0) + NextFile(FileNumber, FileOffset); + SetPlayMode(videoDev, VID_PLAY_CLEAR_BUFFER); + Clear(); } -void cReplayBuffer::SkipAudioBlocks(void) +void cReplayBuffer::GetIndex(int &Current, int &Total, bool SnapToIFrame) { - int Length; + if (index) { - while ((Length = GetAvPesLength()) > 0) { - if (GetAvPesType() == AV_PES_AUDIO) - Skip(Length); - else - break; + LOCK_THREAD; + + if (stillIndex >= 0) + Current = stillIndex; + else { + Current = index->Get(fileName.Number(), fileOffset); + if (SnapToIFrame) { + int i1 = index->GetNextIFrame(Current + 1, false); + int i2 = index->GetNextIFrame(Current, true); + Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2; + } } + Total = index->Last(); + } + else + Current = Total = -1; } bool cReplayBuffer::NextFile(uchar FileNumber, int FileOffset) { if (FileNumber > 0) { Clear(); - if (FileNumber != fileNumber) { - Close(); - fileNumber = FileNumber; - } + fileOffset = FileOffset; + replayFile = fileName.SetOffset(FileNumber, FileOffset); } - if (replayFile >= 0 && EndOfFile()) { + else if (replayFile >= 0 && EndOfFile()) { Close(); - fileNumber++; - if (fileNumber == 0) - esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); + replayFile = fileName.NextFile(); } - if (replayFile < 0) { - sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); - if (access(fileName, R_OK) == 0) { - dsyslog(LOG_INFO, "playing '%s'", fileName); - replayFile = open(fileName, O_RDONLY | O_NONBLOCK); - if (replayFile < 0) { - LOG_ERROR; - return false; + return replayFile >= 0; +} + +void cReplayBuffer::SetTemporalReference(void) +{ + for (int i = 0; i < Available(); i++) { + if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1) { + switch (Byte(i + 3)) { + case SC_PICTURE: { + unsigned short m = (Byte(i + 4) << 8) | Byte(i + 5); + m &= 0x003F; + Set(i + 4, 1, m >> 8); + Set(i + 5, 1, m & 0xFF); + } + return; } - } - else if (errno != ENOENT) - LOG_ERROR; - } - if (replayFile >= 0) { - if (FileOffset >= 0) { - if ((fileOffset = lseek(replayFile, FileOffset, SEEK_SET)) != FileOffset) - LOG_ERROR; - // don't return 'false', maybe we can still replay the file - } - return true; - } - return false; + } + } } int cReplayBuffer::Read(int Max = -1) { - if (stop) - return -1; if (mode != rmPlay) { if (index) { if (Available()) return 0; // write out the entire block - int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileNumber, fileOffset); - if (Index >= 0) { + if (mode == rmStill) { uchar FileNumber; int FileOffset, Length; - if (mode == rmSlowRewind && (brakeCounter++ % 24) != 0) { - // show every I_FRAME 24 times in rmSlowRewind mode to achieve roughly the same speed as in slow forward mode - Index = index->GetNextIFrame(Index, true, &FileNumber, &FileOffset, &Length); // jump ahead one frame - } - Index = index->GetNextIFrame(Index, mode == rmFastForward, &FileNumber, &FileOffset, &Length); - if (Index >= 0) { + if (index->GetNextIFrame(stillIndex + 1, false, &FileNumber, &FileOffset, &Length) >= 0) { if (!NextFile(FileNumber, FileOffset)) return -1; Max = Length; } - lastIndex = Index; + command = rcPause; } - if (Index < 0) { - // This results in normal replay after a fast rewind. - // After a fast forward it will stop. - // TODO Could we cause it to pause at the last frame? - SetMode(rmPlay); - return 0; + else { + int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileName.Number(), fileOffset); + if (Index >= 0) { + if (mode == rmSlowRewind && (brakeCounter++ % 24) != 0) { + // show every I_FRAME 24 times in rmSlowRewind mode to achieve roughly the same speed as in slow forward mode + Index = index->GetNextIFrame(Index, true); // jump ahead one frame + } + uchar FileNumber; + int FileOffset, Length; + Index = index->GetNextIFrame(Index, mode == rmFastForward, &FileNumber, &FileOffset, &Length); + if (Index >= 0) { + if (!NextFile(FileNumber, FileOffset)) + return -1; + Max = Length; + } + lastIndex = Index; + } + if (Index < 0) { + // This results in normal replay after a fast rewind. + // After a fast forward it will stop. + // TODO Could we cause it to pause at the last frame? + SetMode(rmPlay); + return 0; + } } } } @@ -1041,12 +1328,22 @@ int cReplayBuffer::Read(int Max = -1) int readin = 0; do { // If Max is > 0 here we need to make sure we read in the entire block! - int r = cFileBuffer::Read(Max); + int r = cRingBuffer::Read(Max); if (r >= 0) readin += r; else return -1; } while (readin < Max && Free() > 0); + if (mode != rmPlay) { + // delete the audio data in modes other than rmPlay: + int AudioOffset, StartOffset = 0; + while ((AudioOffset = FindStartCode(SC_AUDIO, StartOffset)) >= 0) { + if (!Set(StartOffset + AudioOffset, GetPacketLength(StartOffset + AudioOffset), 0)) + break; // to be able to replay old AV_PES recordings! + StartOffset += AudioOffset; + } + SetTemporalReference(); + } return readin; } if (Available() > 0) @@ -1057,16 +1354,10 @@ int cReplayBuffer::Read(int Max = -1) int cReplayBuffer::Write(int Max) { int Written = 0; - int Av = Available(); - if (skipAudio) { - SkipAudioBlocks(); - Max = GetAvPesLength(); - fileOffset += Av - Available(); - } if (Max) { int w; do { - w = cFileBuffer::Write(Max); + w = cRingBuffer::Write(Max); if (w >= 0) { fileOffset += w; Written += w; @@ -1076,7 +1367,7 @@ int cReplayBuffer::Write(int Max) } else return w; - } while (Max > 0); // we MUST write this entire AV_PES block + } while (Max > 0); // we MUST write this entire frame block } return Written; } @@ -1098,44 +1389,188 @@ cTransferBuffer::cTransferBuffer(int FromDevice, int ToDevice) { fromDevice = FromDevice; toDevice = ToDevice; - active = true; + active = false; Start(); } cTransferBuffer::~cTransferBuffer() { - { - LOCK_THREAD; - active = false; - } - for (time_t t0 = time(NULL); time(NULL) - t0 < 3; ) { - LOCK_THREAD; - if (active) - break; - } + active = false; + Cancel(3); + SetPlayMode(fromDevice, VID_PLAY_RESET); + SetPlayMode(toDevice, VID_PLAY_RESET); } void cTransferBuffer::Action(void) { dsyslog(LOG_INFO, "data transfer thread started (pid=%d)", getpid()); - //XXX hack to make the video device go into 'replaying' mode: - char *dummy = "AV"; // must be "AV" to make the driver go into AV_PES mode! - write(toDevice, dummy, strlen(dummy)); - { - cRingBuffer Buffer(&fromDevice, &toDevice, VIDEOBUFSIZE, 0, 0); - while (active && Buffer.Available() < 100000) { // need to give the read buffer a head start - Buffer.Read(); // initializes fromDevice for reading - usleep(1); // this keeps the CPU load low - } - for (; active;) { + + cRingBuffer Buffer(&fromDevice, &toDevice, VIDEOBUFSIZE, 0, 0); + active = true; + while (active && Buffer.Available() < 100000) { // need to give the read buffer a head start + Buffer.Read(); // initializes fromDevice for reading + usleep(1); // this keeps the CPU load low + } + while (active) { if (Buffer.Read() < 0 || Buffer.Write() < 0) break; usleep(1); // this keeps the CPU load low } - } dsyslog(LOG_INFO, "data transfer thread stopped (pid=%d)", getpid()); - LOCK_THREAD; - active = true; +} + +// --- cCuttingBuffer -------------------------------------------------------- + +class cCuttingBuffer : public cRingBuffer, public cThread { +private: + bool active; + int fromFile, toFile; + cFileName *fromFileName, *toFileName; + cIndexFile *fromIndex, *toIndex; + cMarks fromMarks, toMarks; +protected: + virtual void Action(void); +public: + cCuttingBuffer(const char *FromFileName, const char *ToFileName); + virtual ~cCuttingBuffer(); + }; + +cCuttingBuffer::cCuttingBuffer(const char *FromFileName, const char *ToFileName) +:cRingBuffer(&fromFile, &toFile, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) +{ + active = false; + fromFile = toFile = -1; + fromFileName = toFileName = NULL; + fromIndex = toIndex = NULL; + if (fromMarks.Load(FromFileName) && fromMarks.Count()) { + fromFileName = new cFileName(FromFileName, false); + toFileName = new cFileName(ToFileName, true); + fromIndex = new cIndexFile(FromFileName, false); + toIndex = new cIndexFile(ToFileName, true); + toMarks.Load(ToFileName); // doesn't actually load marks, just sets the file name + Start(); + } + else + esyslog(LOG_ERR, "no editing marks found for %s", FromFileName); +} + +cCuttingBuffer::~cCuttingBuffer() +{ + active = false; + Cancel(3); + delete fromFileName; + delete toFileName; + delete fromIndex; + delete toIndex; +} + +void cCuttingBuffer::Action(void) +{ + dsyslog(LOG_INFO, "video cutting thread started (pid=%d)", getpid()); + + cMark *Mark = fromMarks.First(); + if (Mark) { + fromFile = fromFileName->Open(); + toFile = toFileName->Open(); + active = fromFile >= 0 && toFile >= 0; + int Index = Mark->position; + Mark = fromMarks.Next(Mark); + int FileSize = 0; + int CurrentFileNumber = 0; + int LastIFrame = 0; + toMarks.Add(0); + toMarks.Save(); + while (active) { + uchar FileNumber; + int FileOffset, Length; + uchar PictureType; + + Clear(); // makes sure one frame is completely read and written + + // Read one frame: + + if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) { + if (FileNumber != CurrentFileNumber) { + fromFile = fromFileName->SetOffset(FileNumber, FileOffset); + CurrentFileNumber = FileNumber; + } + if (fromFile >= 0) + Length = cRingBuffer::Read(Length); + else + break; + } + else + break; + + // Write one frame: + + if (PictureType == I_FRAME) { // every file shall start with an I_FRAME + if (FileSize > MAXVIDEOFILESIZE) { + toFile = toFileName->NextFile(); + if (toFile < 0) + break; + FileSize = 0; + } + LastIFrame = 0; + } + cRingBuffer::Write(Length); + toIndex->Write(PictureType, toFileName->Number(), FileSize); + FileSize += Length; + if (!LastIFrame) + LastIFrame = toIndex->Last(); + + // Check editing marks: + + if (Mark && Index >= Mark->position) { + Mark = fromMarks.Next(Mark); + if (Mark) { + Index = Mark->position; + Mark = fromMarks.Next(Mark); + CurrentFileNumber = 0; // triggers SetOffset before reading next frame + toMarks.Add(LastIFrame); + toMarks.Add(toIndex->Last() + 1); + toMarks.Save(); + } + else + break; // final end mark reached + } + } + } + else + esyslog(LOG_ERR, "no editing marks found!"); + dsyslog(LOG_INFO, "end video cutting thread"); +} + +// --- cVideoCutter ---------------------------------------------------------- + +cCuttingBuffer *cVideoCutter::cuttingBuffer = NULL; + +bool cVideoCutter::Start(const char *FileName) +{ + if (!cuttingBuffer) { + const char *EditedVersionName = PrefixVideoFileName(FileName, '%'); + if (EditedVersionName && RemoveVideoFile(EditedVersionName) && MakeDirs(EditedVersionName, true)) { + cuttingBuffer = new cCuttingBuffer(FileName, EditedVersionName); + return true; + } + } + return false; +} + +void cVideoCutter::Stop(void) +{ + delete cuttingBuffer; + cuttingBuffer = NULL; +} + +bool cVideoCutter::Active(void) +{ + if (cuttingBuffer) { + if (cuttingBuffer->Active()) + return true; + Stop(); + } + return false; } // --- cDvbApi --------------------------------------------------------------- @@ -1147,13 +1582,12 @@ cDvbApi *cDvbApi::PrimaryDvbApi = NULL; cDvbApi::cDvbApi(const char *VideoFileName, const char *VbiFileName) { siProcessor = NULL; - pidRecord = pidReplay = 0; - fromRecord = toRecord = -1; - fromReplay = toReplay = -1; - ca = 0; - priority = -1; + recordBuffer = NULL; + replayBuffer = NULL; transferBuffer = NULL; transferringFromDvbApi = NULL; + ca = 0; + priority = -1; videoDev = open(VideoFileName, O_RDWR | O_NONBLOCK); if (videoDev >= 0) { siProcessor = new cSIProcessor(VbiFileName); @@ -1191,8 +1625,6 @@ cDvbApi::cDvbApi(const char *VideoFileName, const char *VbiFileName) #else osd = NULL; #endif - lastProgress = lastTotal = -1; - replayTitle = NULL; currentChannel = 1; } @@ -1201,7 +1633,7 @@ cDvbApi::~cDvbApi() if (videoDev >= 0) { delete siProcessor; Close(); - Stop(); + StopReplay(); StopRecord(); StopTransfer(); OvlO(false); //Overlay off! @@ -1211,7 +1643,6 @@ cDvbApi::~cDvbApi() #if defined(DEBUG_OSD) || defined(REMOTE_KBD) endwin(); #endif - delete replayTitle; } bool cDvbApi::SetPrimaryDvbApi(int n) @@ -1618,8 +2049,6 @@ void cDvbApi::Open(int w, int h) SETCOLOR(clrCyan, 0x00, 0xFC, 0xFC, 255); SETCOLOR(clrMagenta, 0xB0, 0x00, 0xFC, 255); SETCOLOR(clrWhite, 0xFC, 0xFC, 0xFC, 255); - - lastProgress = lastTotal = -1; } void cDvbApi::Close(void) @@ -1633,7 +2062,6 @@ void cDvbApi::Close(void) delete osd; osd = NULL; #endif - lastProgress = lastTotal = -1; } void cDvbApi::Clear(void) @@ -1662,6 +2090,13 @@ void cDvbApi::Fill(int x, int y, int w, int h, eDvbColor color) #endif } +void cDvbApi::SetBitmap(int x, int y, const cBitmap &Bitmap) +{ +#ifndef DEBUG_OSD + osd->SetBitmap(x, y, Bitmap); +#endif +} + void cDvbApi::ClrEol(int x, int y, eDvbColor color) { Fill(x, y, cols - x, 1, color); @@ -1676,6 +2111,15 @@ int cDvbApi::CellWidth(void) #endif } +int cDvbApi::LineHeight(void) +{ +#ifdef DEBUG_OSD + return 1; +#else + return lineHeight; +#endif +} + int cDvbApi::Width(unsigned char c) { #ifdef DEBUG_OSD @@ -1724,67 +2168,12 @@ void cDvbApi::Flush(void) #endif } -bool cDvbApi::ShowProgress(bool Initial) -{ - int Current, Total; - - if (GetIndex(&Current, &Total)) { - if (Initial) { - Clear(); - if (replayTitle) - Text(0, 0, replayTitle); - } - if (Total != lastTotal) - Text(-7, 2, cIndexFile::Str(Total)); - Flush(); -#ifdef DEBUG_OSD - int p = cols * Current / Total; - Fill(0, 1, p, 1, clrGreen); - Fill(p, 1, cols - p, 1, clrWhite); -#else - int w = cols * charWidth; - int p = w * Current / Total; - if (p != lastProgress) { - int y1 = 1 * lineHeight; - int y2 = 2 * lineHeight - 1; - int x1, x2; - eDvbColor color; - if (lastProgress < p) { - x1 = lastProgress + 1; - x2 = p; - if (p >= w) - p = w - 1; - color = clrGreen; - } - else { - x1 = p + 1; - x2 = lastProgress; - color = clrWhite; - } - if (lastProgress < 0) - osd->Fill(0, y1, w - 1, y2, clrWhite); - osd->Fill(x1, y1, x2, y2, color); - lastProgress = p; - } - Flush(); -#endif - Text(0, 2, cIndexFile::Str(Current)); - Flush(); - lastTotal = Total; - return true; - } - return false; -} - bool cDvbApi::SetChannel(int ChannelNumber, int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Ca, int Pnr) { if (videoDev >= 0) { + cThreadLock ThreadLock(siProcessor); // makes sure the siProcessor won't access the vbi-device while switching StopTransfer(); - if (transferringFromDvbApi) { - transferringFromDvbApi->StopTransfer(); - transferringFromDvbApi = NULL; - } - SetReplayMode(VID_PLAY_RESET); + SetPlayMode(videoDev, VID_PLAY_RESET); struct frontend front; ioctl(videoDev, VIDIOCGFRONTEND, &front); unsigned int freq = FrequencyMHz; @@ -1793,8 +2182,7 @@ bool cDvbApi::SetChannel(int ChannelNumber, int FrequencyMHz, char Polarization, freq -= Setup.LnbFrequLo; else freq -= Setup.LnbFrequHi; - front.channel_flags = Ca ? DVB_CHANNEL_CA : DVB_CHANNEL_FTA; - front.pnr = Pnr; + front.pnr = 0; front.freq = freq * 1000000UL; front.diseqc = Diseqc; front.srate = Srate * 1000; @@ -1821,7 +2209,7 @@ bool cDvbApi::SetChannel(int ChannelNumber, int FrequencyMHz, char Polarization, } return true; } - esyslog(LOG_ERR, "ERROR: channel not sync'ed (front.sync=%X)!", front.sync); + esyslog(LOG_ERR, "ERROR: channel %d not sync'ed (front.sync=%X)!", ChannelNumber, front.sync); } return false; } @@ -1843,22 +2231,31 @@ void cDvbApi::StopTransfer(void) if (transferBuffer) { delete transferBuffer; transferBuffer = NULL; - SetReplayMode(VID_PLAY_RESET); + SetPlayMode(videoDev, VID_PLAY_RESET); + } + if (transferringFromDvbApi) { + transferringFromDvbApi->StopTransfer(); + transferringFromDvbApi = NULL; } } +int cDvbApi::SecondsToFrames(int Seconds) +{ + return Seconds * FRAMESPERSEC; +} + bool cDvbApi::Recording(void) { - if (pidRecord && !CheckProcess(pidRecord)) - pidRecord = 0; - return pidRecord; + if (recordBuffer && !recordBuffer->Active()) + StopRecord(); + return recordBuffer != NULL; } bool cDvbApi::Replaying(void) { - if (pidReplay && !CheckProcess(pidReplay)) - pidReplay = 0; - return pidReplay; + if (replayBuffer && !replayBuffer->Active()) + StopReplay(); + return replayBuffer != NULL; } bool cDvbApi::StartRecord(const char *FileName, int Ca, int Priority) @@ -1871,7 +2268,7 @@ bool cDvbApi::StartRecord(const char *FileName, int Ca, int Priority) StopTransfer(); - Stop(); // TODO: remove this if the driver is able to do record and replay at the same time + StopReplay(); // TODO: remove this if the driver is able to do record and replay at the same time // Check FileName: @@ -1886,129 +2283,41 @@ bool cDvbApi::StartRecord(const char *FileName, int Ca, int Priority) if (!MakeDirs(FileName, true)) return false; - // Open pipes for recording process: + // Create recording buffer: - int fromRecordPipe[2], toRecordPipe[2]; + recordBuffer = new cRecordBuffer(&videoDev, FileName); - if (pipe(fromRecordPipe) != 0) { - LOG_ERROR; - return false; - } - if (pipe(toRecordPipe) != 0) { - LOG_ERROR; - return false; - } - - // Create recording process: - - pidRecord = fork(); - if (pidRecord < 0) { - LOG_ERROR; - return false; - } - if (pidRecord == 0) { - - // This is the actual recording process - - dsyslog(LOG_INFO, "start recording process (pid=%d)", getpid()); - bool DataStreamBroken = false; - int fromMain = toRecordPipe[0]; - int toMain = fromRecordPipe[1]; - cRecordBuffer *Buffer = new cRecordBuffer(&videoDev, FileName); - if (Buffer) { - for (;;) { - fd_set set; - FD_ZERO(&set); - FD_SET(videoDev, &set); - FD_SET(fromMain, &set); - struct timeval timeout; - timeout.tv_sec = 1; - timeout.tv_usec = 0; - bool ForceEnd = false; - if (select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0) { - if (FD_ISSET(videoDev, &set)) { - if (Buffer->Read() < 0) - break; - DataStreamBroken = false; - } - if (FD_ISSET(fromMain, &set)) { - switch (readchar(fromMain)) { - case dvbStop: Buffer->Stop(); - ForceEnd = DataStreamBroken; - break; - } - } - } - else { - DataStreamBroken = true; - esyslog(LOG_ERR, "ERROR: video data stream broken"); - } - if (Buffer->WriteWithTimeout(ForceEnd) < 0) - break; - } - delete Buffer; - } - else - esyslog(LOG_ERR, "ERROR: can't allocate recording buffer"); - close(fromMain); - close(toMain); - dsyslog(LOG_INFO, "end recording process"); - exit(0); + if (recordBuffer) { + ca = Ca; + priority = Priority; + return true; } - - // Establish communication with the recording process: - - fromRecord = fromRecordPipe[0]; - toRecord = toRecordPipe[1]; - - ca = Ca; - priority = Priority; - return true; + else + esyslog(LOG_ERR, "ERROR: can't allocate recording buffer"); } return false; } void cDvbApi::StopRecord(void) { - if (pidRecord) { - writechar(toRecord, dvbStop); - close(toRecord); - close(fromRecord); - toRecord = fromRecord = -1; - KillProcess(pidRecord); - pidRecord = 0; + if (recordBuffer) { + delete recordBuffer; + recordBuffer = NULL; ca = 0; priority = -1; - SetReplayMode(VID_PLAY_RESET); //XXX } } -void cDvbApi::SetReplayMode(int Mode) -{ - if (videoDev >= 0) { - struct video_play_mode pmode; - pmode.mode = Mode; - ioctl(videoDev, VIDIOCSPLAYMODE, &pmode); - } -} - -bool cDvbApi::StartReplay(const char *FileName, const char *Title) +bool cDvbApi::StartReplay(const char *FileName) { if (Recording()) { esyslog(LOG_ERR, "ERROR: StartReplay() called while recording - ignored!"); return false; } StopTransfer(); - Stop(); + StopReplay(); if (videoDev >= 0) { - lastProgress = lastTotal = -1; - delete replayTitle; - if (Title) { - if ((replayTitle = strdup(Title)) == NULL) - esyslog(LOG_ERR, "ERROR: StartReplay: can't copy title '%s'", Title); - } - // Check FileName: if (!FileName) { @@ -2017,209 +2326,77 @@ bool cDvbApi::StartReplay(const char *FileName, const char *Title) } isyslog(LOG_INFO, "replay %s", FileName); - // Open pipes for replay process: - - int fromReplayPipe[2], toReplayPipe[2]; - - if (pipe(fromReplayPipe) != 0) { - LOG_ERROR; - return false; - } - if (pipe(toReplayPipe) != 0) { - LOG_ERROR; - return false; - } - - // Create replay process: + // Create replay buffer: - pidReplay = fork(); - if (pidReplay < 0) { - LOG_ERROR; - return false; - } - if (pidReplay == 0) { - - // This is the actual replaying process - - dsyslog(LOG_INFO, "start replaying process (pid=%d)", getpid()); - int fromMain = toReplayPipe[0]; - int toMain = fromReplayPipe[1]; - cReplayBuffer *Buffer = new cReplayBuffer(&videoDev, FileName); - if (Buffer) { - bool Paused = false; - bool FastForward = false; - bool FastRewind = false; - int ResumeIndex = Buffer->Resume(); - if (ResumeIndex >= 0) - isyslog(LOG_INFO, "resuming replay at index %d (%s)", ResumeIndex, cIndexFile::Str(ResumeIndex, true)); - for (;;) { - if (Buffer->Read() < 0) - break; - fd_set setIn, setOut; - FD_ZERO(&setIn); - FD_ZERO(&setOut); - FD_SET(fromMain, &setIn); - FD_SET(videoDev, &setOut); - struct timeval timeout; - timeout.tv_sec = 1; - timeout.tv_usec = 0; - if (select(FD_SETSIZE, &setIn, &setOut, NULL, &timeout) > 0) { - if (FD_ISSET(videoDev, &setOut)) { - if (Buffer->Write() < 0) - break; - } - if (FD_ISSET(fromMain, &setIn)) { - switch (readchar(fromMain)) { - case dvbStop: SetReplayMode(VID_PLAY_CLEAR_BUFFER); - Buffer->Stop(); - break; - case dvbPause: SetReplayMode(Paused ? VID_PLAY_NORMAL : VID_PLAY_PAUSE); - Paused = !Paused; - if (FastForward || FastRewind) { - SetReplayMode(VID_PLAY_CLEAR_BUFFER); - Buffer->Clear(); - } - FastForward = FastRewind = false; - Buffer->SetMode(rmPlay); - break; - case dvbPlay: if (FastForward || FastRewind || Paused) { - SetReplayMode(VID_PLAY_CLEAR_BUFFER); - SetReplayMode(VID_PLAY_NORMAL); - FastForward = FastRewind = Paused = false; - Buffer->SetMode(rmPlay); - } - break; - case dvbForward: SetReplayMode(VID_PLAY_CLEAR_BUFFER); - Buffer->Clear(); - FastForward = !FastForward; - FastRewind = false; - if (Paused) { - Buffer->SetMode(rmPlay); - SetReplayMode(FastForward ? VID_PLAY_SLOW_MOTION : VID_PLAY_PAUSE); - } - else { - SetReplayMode(VID_PLAY_NORMAL); - Buffer->SetMode(FastForward ? rmFastForward : rmPlay); - } - break; - case dvbBackward: SetReplayMode(VID_PLAY_CLEAR_BUFFER); - Buffer->Clear(); - FastRewind = !FastRewind; - FastForward = false; - if (Paused) { - Buffer->SetMode(FastRewind ? rmSlowRewind : rmPlay); - SetReplayMode(FastRewind ? VID_PLAY_NORMAL : VID_PLAY_PAUSE); - } - else { - SetReplayMode(VID_PLAY_NORMAL); - Buffer->SetMode(FastRewind ? rmFastRewind : rmPlay); - } - break; - case dvbSkip: { - int Seconds; - if (readint(fromMain, Seconds)) { - SetReplayMode(VID_PLAY_CLEAR_BUFFER); - SetReplayMode(VID_PLAY_NORMAL); - FastForward = FastRewind = Paused = false; - Buffer->SetMode(rmPlay); - Buffer->SkipSeconds(Seconds); - } - } - break; - case dvbGetIndex: { - int Current, Total; - Buffer->GetIndex(Current, Total); - writeint(toMain, Current); - writeint(toMain, Total); - } - break; - } - } - } - } - Buffer->Save(); - delete Buffer; - } - else - esyslog(LOG_ERR, "ERROR: can't allocate replaying buffer"); - close(fromMain); - close(toMain); - SetReplayMode(VID_PLAY_RESET); //XXX - dsyslog(LOG_INFO, "end replaying process"); - exit(0); - } - - // Establish communication with the replay process: - - fromReplay = fromReplayPipe[0]; - toReplay = toReplayPipe[1]; - return true; + replayBuffer = new cReplayBuffer(&videoDev, FileName); + if (replayBuffer) + return true; + else + esyslog(LOG_ERR, "ERROR: can't allocate replaying buffer"); } return false; } -void cDvbApi::Stop(void) +void cDvbApi::StopReplay(void) { - if (pidReplay) { - writechar(toReplay, dvbStop); - close(toReplay); - close(fromReplay); - toReplay = fromReplay = -1; - KillProcess(pidReplay); - pidReplay = 0; - SetReplayMode(VID_PLAY_RESET); //XXX + if (replayBuffer) { + delete replayBuffer; + replayBuffer = NULL; } } void cDvbApi::Pause(void) { - if (pidReplay) - writechar(toReplay, dvbPause); + if (replayBuffer) + replayBuffer->Pause(); } void cDvbApi::Play(void) { - if (pidReplay) - writechar(toReplay, dvbPlay); + if (replayBuffer) + replayBuffer->Play(); } void cDvbApi::Forward(void) { - if (pidReplay) - writechar(toReplay, dvbForward); + if (replayBuffer) + replayBuffer->Forward(); } void cDvbApi::Backward(void) { - if (pidReplay) - writechar(toReplay, dvbBackward); + if (replayBuffer) + replayBuffer->Backward(); } -void cDvbApi::Skip(int Seconds) +void cDvbApi::SkipSeconds(int Seconds) { - if (pidReplay) { - writechar(toReplay, dvbSkip); - writeint(toReplay, Seconds); - } + if (replayBuffer) + replayBuffer->SkipSeconds(Seconds); } -bool cDvbApi::GetIndex(int *Current, int *Total) +int cDvbApi::SkipFrames(int Frames) { - if (pidReplay) { - int total; - purge(fromReplay); - writechar(toReplay, dvbGetIndex); - if (readint(fromReplay, *Current) && readint(fromReplay, total)) { - if (Total) - *Total = total; - } - else - *Current = -1; - return *Current >= 0; + if (replayBuffer) + return replayBuffer->SkipFrames(Frames); + return -1; +} + +bool cDvbApi::GetIndex(int &Current, int &Total, bool SnapToIFrame) +{ + if (replayBuffer) { + replayBuffer->GetIndex(Current, Total, SnapToIFrame); + return true; } return false; } +void cDvbApi::Goto(int Position, bool Still) +{ + if (replayBuffer) + replayBuffer->Goto(Position, Still); +} + // --- cEITScanner ----------------------------------------------------------- cEITScanner::cEITScanner(void) @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.h 1.26 2000/11/19 14:09:41 kls Exp $ + * $Id: dvbapi.h 1.30 2001/01/07 15:56:10 kls Exp $ */ #ifndef __DVBAPI_H @@ -22,6 +22,7 @@ typedef unsigned char __u8; #include <dvb.h> #include "dvbosd.h" #include "eit.h" +#include "thread.h" // Overlay facilities #define MAXCLIPRECTS 100 @@ -42,7 +43,24 @@ public: bool Save(int Index); }; +const char *IndexToHMSF(int Index, bool WithFrame = false); + // Converts the given index to a string, optionally containing the frame number. +int HMSFToIndex(const char *HMSF); + // Converts the given string (format: "hh:mm:ss.ff") to an index. + +class cRecordBuffer; +class cReplayBuffer; class cTransferBuffer; +class cCuttingBuffer; + +class cVideoCutter { +private: + static cCuttingBuffer *cuttingBuffer; +public: + static bool Start(const char *FileName); + static void Stop(void); + static bool Active(void); + }; class cDvbApi { private: @@ -129,22 +147,16 @@ public: void Close(void); void Clear(void); void Fill(int x, int y, int w, int h, eDvbColor color = clrBackground); + void SetBitmap(int x, int y, const cBitmap &Bitmap); void ClrEol(int x, int y, eDvbColor color = clrBackground); int CellWidth(void); + int LineHeight(void); int Width(unsigned char c); int WidthInCells(const char *s); eDvbFont SetFont(eDvbFont Font); void Text(int x, int y, const char *s, eDvbColor colorFg = clrWhite, eDvbColor colorBg = clrBackground); void Flush(void); - // Progress Display facilities - -private: - int lastProgress, lastTotal; - char *replayTitle; -public: - bool ShowProgress(bool Initial = false); - // Channel facilities private: @@ -171,20 +183,10 @@ private: // Record/Replay facilities private: - enum { dvbStop = 1, // let's not have 0 as a command - dvbPause, - dvbPlay, - dvbForward, - dvbBackward, - dvbSkip, - dvbGetIndex, - }; - pid_t pidRecord, pidReplay; - int fromRecord, toRecord; - int fromReplay, toReplay; + cRecordBuffer *recordBuffer; + cReplayBuffer *replayBuffer; int ca; int priority; - void SetReplayMode(int Mode); protected: int Ca(void) { return ca; } // Returns the ca of the current recording session (0..MAXDVBAPI). @@ -192,6 +194,8 @@ protected: // Returns the priority of the current recording session (0..99), // or -1 if no recording is currently active. public: + int SecondsToFrames(int Seconds); + // Returns the number of frames corresponding to the given number of seconds. bool Recording(void); // Returns true if we are currently recording. bool Replaying(void); @@ -209,12 +213,11 @@ public: // returned. void StopRecord(void); // Stops the current recording session (if any). - bool StartReplay(const char *FileName, const char *Title = NULL); + bool StartReplay(const char *FileName); // Starts replaying the given file. // If there is already a replay session active, it will be stopped // and the new file will be played back. - // If provided Title will be used in the progress display. - void Stop(void); + void StopReplay(void); // Stops the current replay session (if any). void Pause(void); // Pauses the current replay session, or resumes a paused session. @@ -224,12 +227,21 @@ public: // Runs the current replay session forward at a higher speed. void Backward(void); // Runs the current replay session backwards at a higher speed. - void Skip(int Seconds); + void SkipSeconds(int Seconds); // Skips the given number of seconds in the current replay session. // The sign of 'Seconds' determines the direction in which to skip. // Use a very large negative value to go all the way back to the // beginning of the recording. - bool GetIndex(int *Current, int *Total = NULL); + int SkipFrames(int Frames); + // Returns the new index into the current replay session after skipping + // the given number of frames (no actual repositioning is done!). + // The sign of 'Frames' determines the direction in which to skip. + bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false); + // Returns the current and total frame index, optionally snapped to the + // nearest I-frame. + void Goto(int Index, bool Still = false); + // Positions to the given index and displays that frame as a still picture + // if Still is true. }; class cEITScanner { @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbosd.c 1.6 2000/11/18 15:36:51 kls Exp $ + * $Id: dvbosd.c 1.7 2000/12/09 11:13:00 kls Exp $ */ #include "dvbosd.h" @@ -82,6 +82,16 @@ void cBitmap::SetPixel(int x, int y, eDvbColor Color) } } +void cBitmap::SetBitmap(int x, int y, const cBitmap &Bitmap) +{ + if (bitmap && Bitmap.bitmap) { + for (int ix = 0; ix < Bitmap.width; ix++) { + for (int iy = 0; iy < Bitmap.height; iy++) + SetPixel(x + ix, y + iy, eDvbColor(Bitmap.bitmap[Bitmap.width * iy + ix])); + } + } +} + int cBitmap::Width(unsigned char c) { return font ? font->Width(c) : -1; @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: dvbosd.h 1.4 2000/11/18 15:25:25 kls Exp $ + * $Id: dvbosd.h 1.5 2000/12/09 10:32:47 kls Exp $ */ #ifndef __DVBOSD_H @@ -57,6 +57,7 @@ public: eDvbFont SetFont(eDvbFont Font); bool Dirty(void); void SetPixel(int x, int y, eDvbColor Color); + void SetBitmap(int x, int y, const cBitmap &Bitmap); int Width(unsigned char c); int Width(const char *s); void Text(int x, int y, const char *s, eDvbColor ColorFg = clrWhite, eDvbColor ColorBg = clrBackground); @@ -13,7 +13,7 @@ * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * - * $Id: eit.c 1.9 2000/11/18 13:42:28 kls Exp $ + * $Id: eit.c 1.11 2000/12/03 15:33:37 kls Exp $ ***************************************************************************/ #include "eit.h" @@ -33,6 +33,8 @@ #include <sys/types.h> #include <time.h> #include <unistd.h> +#include "config.h" +#include "videodir.h" // --- cMJD ------------------------------------------------------------------ @@ -129,7 +131,7 @@ bool cMJD::SetSystemTime() isyslog(LOG_INFO, "System Time = %s (%ld)\n", ctime(&loctim), loctim); isyslog(LOG_INFO, "Local Time = %s (%ld)\n", ctime(&mjdtime), mjdtime); if (stime(&mjdtime) < 0) - esyslog(LOG_ERR, "ERROR while setting system time: %s", strerror(errno)); + esyslog(LOG_ERR, "ERROR while setting system time: %m"); return true; } @@ -393,6 +395,21 @@ unsigned short cEventInfo::GetServiceID() const return uServiceID; } +/** */ +void cEventInfo::Dump(FILE *f) const +{ + if (tTime + lDuration >= time(NULL)) { + fprintf(f, "E %u %ld %ld\n", uEventID, tTime, lDuration); + if (!isempty(pTitle)) + fprintf(f, "T %s\n", pTitle); + if (!isempty(pSubtitle)) + fprintf(f, "S %s\n", pSubtitle); + if (!isempty(pExtendedDescription)) + fprintf(f, "D %s\n", pExtendedDescription); + fprintf(f, "e\n"); + } +} + // --- cSchedule ------------------------------------------------------------- cSchedule::cSchedule(unsigned short servid) @@ -529,6 +546,19 @@ void cSchedule::Cleanup(time_t tTime) } } +/** */ +void cSchedule::Dump(FILE *f) const +{ + cChannel *channel = Channels.GetByServiceID(uServiceID); + if (channel) + { + fprintf(f, "C %u %s\n", uServiceID, channel->name); + for (cEventInfo *p = Events.First(); p; p = Events.Next(p)) + p->Dump(f); + fprintf(f, "c\n"); + } +} + // --- cSchedules ------------------------------------------------------------ cSchedules::cSchedules() @@ -590,6 +620,13 @@ void cSchedules::Cleanup() } } +/** */ +void cSchedules::Dump(FILE *f) const +{ + for (cSchedule *p = First(); p; p = Next(p)) + p->Dump(f); +} + // --- cEIT ------------------------------------------------------------------ #define DEC(N) dec << setw(N) << setfill(int('0')) @@ -1080,7 +1117,7 @@ cSIProcessor::~cSIProcessor() { if (fsvbi >= 0) { - Stop(); + Cancel(); ShutDownFilters(); delete filters; if (!--numSIProcessors) // the last one deletes it @@ -1105,6 +1142,7 @@ void cSIProcessor::Action() unsigned int seclen; unsigned int pid; time_t lastCleanup = time(NULL); + time_t lastDump = time(NULL); struct pollfd pfd; while(true) @@ -1123,6 +1161,19 @@ void cSIProcessor::Action() schedulesMutex.Unlock(); lastCleanup = now; } + if (now - lastDump > 600) + { + LOCK_THREAD; + + schedulesMutex.Lock(); + FILE *f = fopen(AddDirectory(VideoDirectory, "epg.data"), "w"); + if (f) { + schedules->Dump(f); + fclose(f); + } + lastDump = now; + schedulesMutex.Unlock(); + } } /* wait data become ready from the bitfilter */ @@ -1283,4 +1334,3 @@ bool cSIProcessor::RefreshFilters() return ret; } - @@ -13,7 +13,7 @@ * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * - * $Id: eit.h 1.3 2000/11/17 16:14:27 kls Exp $ + * $Id: eit.h 1.4 2000/11/24 14:35:22 kls Exp $ ***************************************************************************/ #ifndef __EIT_H @@ -66,6 +66,7 @@ public: unsigned short GetServiceID(void) const; int GetChannelNumber(void) const { return nChannelNumber; } void SetChannelNumber(int ChannelNumber) const { ((cEventInfo *)this)->nChannelNumber = ChannelNumber; } // doesn't modify the EIT data, so it's ok to make it 'const' + void Dump(FILE *f) const; }; class cSchedule : public cListObject { @@ -92,6 +93,7 @@ public: const cEventInfo *GetEvent(time_t tTime) const; const cEventInfo *GetEventNumber(int n) const { return Events.Get(n); } int NumEvents(void) const { return Events.Count(); } + void Dump(FILE *f) const; }; class cSchedules : public cList<cSchedule> { @@ -107,6 +109,7 @@ public: ~cSchedules(); const cSchedule *GetSchedule(unsigned short servid) const; const cSchedule *GetSchedule(void) const; + void Dump(FILE *f) const; }; typedef struct sip_filter { diff --git a/epg2html.pl b/epg2html.pl new file mode 100644 index 0000000..215eb73 --- /dev/null +++ b/epg2html.pl @@ -0,0 +1,96 @@ +#!/usr/bin/perl + +# A simple EPG to HTML converter +# +# Converts the EPG data written by 'vdr' into the file /video/epg.data +# into a simple HTML programme listing, consisting of one file per channel +# plus an 'index.htm' file. All output files are written into the current +# directory. +# +# Usage: epg2html.pl < /video/epg.data +# +# See the main source file 'vdr.c' for copyright information and +# how to reach the author. +# +# $Id: epg2html.pl 1.2 2000/12/01 18:37:46 kls Exp $ + +@Index = (); + +sub GetDay +{ + return substr(localtime(shift), 0, 10); +} + +sub GetTime +{ + return substr(localtime(shift), 11, 5); +} + +sub Tags +{ + my $s = shift; + $s =~ s/\&/&/g; + $s =~ s/</</g; + $s =~ s/>/>/g; + return $s; +} + +while (<>) { + chomp; + if (/^C ([^ ]+) *(.*)/) { + my $Channel = $2; + (my $Page = $Channel) =~ y/\/ /-_/; + $Page .= ".htm"; + $Channel = Tags($Channel); + push(@Index, qq{<a href="$Page">$Channel</a><br>\n}); + my %Events = (); + while (<>) { + if (/^E (.*) (.*) (.*)/) { + (my $Time, $Duration) = ($2, $3); + my $Title = "", $Subtitle = "", $Description = ""; + while (<>) { + if (/^T (.*)/) { $Title = Tags($1); } + elsif (/^S (.*)/) { $Subtitle = Tags($1); } + elsif (/^D (.*)/) { $Description = Tags($1); } + elsif (/^e/) { + $Events{$Time} = [($Duration, $Title, $Subtitle, $Description)]; + last; + } + } + } + elsif (/^c/) { + my @Schedule = (); + my $Day = ""; + for $t (sort keys %Events) { + (my $Duration, $Title, $Subtitle, $Description) = @{$Events{$t}}; + my $d = GetDay($t); + if ($d ne $Day) { + push(@Schedule, "</table>\n") if ($Day && @Schedule); + push(@Schedule, "<h2>$d</h2>\n"); + push(@Schedule, "<table cellspacing=2>\n"); + $Day = $d; + } + my $Entry = $Title; + $Entry .= "<br><i>$Subtitle</i>" if $Subtitle; + $Entry .= "<br>$Description" if $Description; + push(@Schedule, "<tr><td valign=top>" . GetTime($t) . "</td><td>$Entry</td></tr>\n"); + } + push(@Schedule, "</table>\n") if (@Schedule); + open(PAGE, ">$Page") or die "$Page: $!\n"; + print PAGE "<html>\n<head><title>$Channel</title><head>\n<body>\n"; + print PAGE "<h1>$Channel</h1>\n"; + print PAGE @Schedule; + print PAGE "</body>\n</html>\n"; + close(PAGE); + last; + } + } + } + } + +open(INDEX, ">index.htm") or die "index.htm: $!\n"; +print INDEX "<html>\n<head><title>EPG Index</title><head>\n<body>\n"; +print INDEX sort { lc($a) cmp lc($b) } @Index; +print INDEX "</body>\n</html>\n"; +close(INDEX); + @@ -4,9 +4,10 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: i18n.c 1.6 2000/11/19 12:12:53 kls Exp $ + * $Id: i18n.c 1.8 2001/01/06 16:17:39 kls Exp $ * * Slovenian translations provided by Miha Setina <mihasetina@softhome.net> + * Italian translations provided by Alberto Carraro <bertocar@tin.it> * */ @@ -31,9 +32,8 @@ * "Italiano", * }, * - * and so on. Insert your language so that all the entries - * following 'English' will be sorted alphabetically, and write - * the name of your language in your language (not in English, + * and so on. Append your language after the last existing language + * and write the name of your language in your language (not in English, * which means that it should be 'Italiano', not 'Italian'). * Note that only the characters defined in 'fontosd.c' will * be available! @@ -48,7 +48,7 @@ #include "config.h" #include "tools.h" -const int NumLanguages = 3; +const int NumLanguages = 4; typedef const char *tPhrase[NumLanguages]; @@ -57,401 +57,523 @@ const tPhrase Phrases[] = { { "English", "Deutsch", "Slovenski", + "Italiano", }, // Menu titles: { "Main", "Hauptmenü", "Glavni meni", + "Principale", }, { "Schedule", "Programm", "Urnik", + "Programmi", }, { "Channels", "Kanäle", "Kanali", + "Canali", }, { "Timers", "Timer", "Termini", + "Timer", }, { "Recordings", "Aufzeichnungen", "Posnetki", + "Registrazioni", }, { "Setup", "Einstellungen", "Nastavitve", + "Opzioni", }, { "Commands", "Befehle", "Ukazi", + "Comandi", }, { "Edit Channel", "Kanal Editieren", "Uredi kanal", + "Modifica canale", }, { "Edit Timer", "Timer Editieren", "Uredi termin", + "Modifica Timer", }, { "Event", "Sendung", "Oddaja", + "Eventi", }, { "Summary", "Inhalt", "Vsebina", + "Sommario", }, { "Schedule - %s", "Programm - %s", "Urnik - %s", + "Programma - %s", }, { "What's on now?", "Was läuft jetzt?", "Kaj je na sporedu?", + "In programmazione", }, { "What's on next?", "Was läuft als nächstes?", "Kaj sledi?", + "Prossimi programmi", }, // Button texts (must not be more than 10 characters!): { "Edit", "Editieren", "Uredi", + "Modifica", }, { "New", "Neu", "Novo", + "Nuovo", }, { "Delete", "Löschen", "Odstrani", + "Cancella", }, { "Mark", "Markieren", "Oznaci", + "Marca", }, { "Record", "Aufnehmen", "Posnemi", + "Registra", }, { "Play", "Wiedergabe", "Predavajaj", + "Riproduci", }, { "Resume", "Weiter", "Nadaljuj", + "Riprendi", }, { "Summary", "Inhalt", "Vsebina", + "Sommario", }, { "Switch", "Umschalten", "Preklopi", + "Cambia", }, { "Now", "Jetzt", "Sedaj", + "Adesso", }, { "Next", "Nächste", "Naslednji", + "Prossimo", }, { "Schedule", "Programm", "Urnik", + "Programma", }, // Confirmations: - { "Delete Channel?", + { "Delete channel?", "Kanal löschen?", "Odstrani kanal?", + "Cancello il canale?", }, - { "Delete Timer?", + { "Delete timer?", "Timer löschen?", "Odstani termin?", + "Cancello il timer?", }, - { "Delete Recording?", + { "Delete recording?", "Aufzeichnung löschen?", "Odstrani posnetek?", + "Cancello la registrazione?", }, - { "Stop Recording?", + { "Stop recording?", "Aufzeichnung beenden?", "Koncaj snemanje?", + "Fermo la registrazione?", + }, + { "Cancel editing?", + "Schneiden abbrechen?", + "Zelite prekiniti urejanje?", + "Annullo la modifica?", }, // Channel parameters: { "Name", "Name", "Naziv", + "Nome", }, { "Frequency", "Frequenz", "Frekvenca", + "Frequenza", }, { "Polarization", "Polarisation", "Polarizacija", + "Polarizzazione", }, { "Diseqc", "Diseqc", "Diseqc", + "Diseqc", }, { "Srate", "Srate", "Srate", + "Srate", }, { "Vpid", "Vpid", "Vpid", + "Vpid", }, { "Apid", "Apid", "Apid", + "Apid", }, { "CA", "CA", "CA", + "CA", }, { "Pnr", "Pnr", "Pnr", + "Pnr", }, // Timer parameters: { "Active", "Aktiv", "Aktivno", + "Attivo", }, { "Channel", "Kanal", "Kanal", + "Canale", }, { "Day", "Tag", "Dan", + "Giorno", }, { "Start", "Anfang", "Zacetek", + "Inizio", }, { "Stop", "Ende", "Konec", + "Fine", }, { "Priority", "Priorität", "Prioriteta", + "Priorita", }, { "Lifetime", "Lebensdauer", "Veljavnost", + "Durata", }, { "File", "Datei", "Datoteka", + "Nome", }, // Error messages: { "Channel is being used by a timer!", "Kanal wird von einem Timer benutzt!", "Urnik zaseda kanal!", + "Canale occupato da un timer!", }, { "Can't switch channel!", "Kanal kann nicht umgeschaltet werden!", "Ne morem preklopiti kanala!", + "Impossibile cambiare canale!", }, { "Timer is recording!", "Timer zeichnet gerade auf!", "Snemanje po urniku!", + "Registrazione di un timer in corso!", }, { "Error while deleting recording!", "Fehler beim Löschen der Aufzeichnung!", "Napaka pri odstranjevanju posnetka!", + "Errore durante la canc del filmato!", }, { "*** Invalid Channel ***", "*** Ungültiger Kanal ***", "*** Neznan kanal ***", + "*** CANALE INVALIDO ***", }, { "No free DVB device to record!", "Keine freie DVB-Karte zum Aufnehmen!", "Ni proste DVB naprave za snemanje!", + "Nessuna card DVB disp per registrare!", }, { "Channel locked (recording)!", "Kanal blockiert (zeichnet auf)!", "Zaklenjen kanal (snemanje)!", + "Canale bloccato (in registrazione)!", + }, + { "Can't start editing process!", + "Schnitt kann nicht gestartet werden!", + "Ne morem zaceti urejanja!", + "Imposs iniziare processo di modifica", + }, + { "Editing process already active!", + "Schnitt bereits aktiv!", + "Urejanje je ze aktivno!", + "Processo di modifica gia` attivo", }, // Setup parameters: { "OSD-Language", "OSD-Sprache", "OSD-jezik", + "Linguaggio OSD", }, { "PrimaryDVB", "Primäres Interface", "Primarna naprava", + "Scheda DVB primaria", }, { "ShowInfoOnChSwitch", "Info zeigen", "Pokazi naziv kanala", + "Vis info nel cambio canale", }, { "MenuScrollPage", "Seitenweise scrollen", "Drsni meni", + "Scrolla pagina nel menu", }, { "MarkInstantRecord", "Direktaufz. markieren", "Oznaci direktno snemanje", + "Marca la registrazione", }, { "LnbFrequLo", "Untere LNB-Frequenz", "Spodnja LNB-frek.", + "Freq LO LNB", }, { "LnbFrequHi", "Obere LNB-Frequenz", "Zgornja LNB-frek.", + "Freq HI LNB", }, { "SetSystemTime", "Systemzeit stellen", "Sistemski cas", + "Setta orario auto", }, { "MarginStart", "Zeitpuffer bei Anfang", "Premor pred zacetkom", + "Min margine inizio", }, { "MarginStop", "Zeitpuffer bei Ende", "Premor za koncem", + "Min margine fine", }, { "EPGScanTimeout", "Zeit bis EPG Scan", "Cas do EPG pregleda", + "Timeout EPG", }, // The days of the week: { "MTWTFSS", "MDMDFSS", "PTSCPSN", + "DLMMGVS", }, // Learning keys: { "Learning Remote Control Keys", "Fernbedienungs-Codes lernen", "Ucim se kod upravljalca", + "Apprendimento tasti unita` remota", }, { "Phase 1: Detecting RC code type", "Phase 1: FB Code feststellen", "Faza 1: Sprejemanje IR kode", + "Fase 1: tipo ricevitore RC", }, { "Press any key on the RC unit", "Eine Taste auf der FB drücken", "Pritisnite tipko na upravljalcu", + "Premere un tasto nell'unita` RC", }, { "RC code detected!", "FB Code erkannt!", "IR koda sprejeta!", + "Codice RC rilevato!", }, { "Do not press any key...", "Keine Taste drücken...", "Ne pritiskajte tipk...", + "Non premere alcun tasto...", }, { "Phase 2: Learning specific key codes", "Phase 2: Einzelne Tastencodes lernen", "Faza 2: Ucenje posebnih kod", + "Fase 2: Codici specifici dei tasti", }, { "Press key for '%s'", "Taste für '%s' drücken", "Pritisnite tipko za '%s'", + "Premere il tasto per '%s'", }, { "Press 'Up' to confirm", "'Auf' drücken zum Bestätigen", "Pritisnite tipko 'Gor' za potrditev", + "Premere 'Su' per confermare", }, { "Press 'Down' to continue", "'Ab' drücken zum Weitermachen", "Pritisnite tipko 'Dol' za nadaljevanje", + "Premere 'Giu' per confermare", }, { "(press 'Up' to go back)", "('Auf' drücken um zurückzugehen)", "(pritisnite 'Gor' za nazaj)", + "(premere 'Su' per tornare indietro)", }, { "(press 'Down' to end key definition)", "('Ab' drücken zum Beenden", "(pritisnite 'Dol' za konec)", + "('Giu' per finire la definiz tasti)", }, { "Phase 3: Saving key codes", "Phase 3: Codes abspeichern", "Faza 3: Shranjujem kodo", + "Fase 3: Salvataggio key codes", }, { "Press 'Up' to save, 'Down' to cancel", "'Auf' speichert, 'Ab' bricht ab", "'Gor' za potrditev, 'Dol' za prekinitev", + "'Su' per salvare, 'Giu' per annullare", }, // Key names: { "Up", "Auf", "Gor", + "Su", }, { "Down", "Ab", "Dol", + "Giu", }, { "Menu", "Menü", "Meni", + "Menu", }, { "Ok", "Ok", "Ok", + "Ok", }, { "Back", "Zurück", "Nazaj", + "Indietro", }, { "Left", "Links", "Levo", + "Sinistra", }, { "Right", "Rechts", "Desno", + "Destra", }, { "Red", "Rot", "Rdeca", + "Rosso", }, { "Green", "Grün", "Zelena", + "Verde", }, { "Yellow", "Gelb", "Rumena", + "Giallo", }, { "Blue", "Blau", "Modra", + "Blu", }, // Miscellaneous: { "yes", "ja", "da", + "si", }, { "no", "nein", "ne", + "no", }, { "Stop replaying", "Wiedergabe beenden", "Prekini ponavljanje", + "Interrompi riproduzione", }, { "Stop recording ", // note the trailing blank! "Aufzeichnung beenden ", "Prekini shranjevanje ", + "Interrompi registrazione ", + }, + { "Cancel editing", + "Schneiden abbrechen", + "Prekini urejanje", + "Annulla modifiche", }, { "Switching primary DVB...", "Primäres Interface wird umgeschaltet...", "Preklapljanje primarne naprave...", + "Cambio su card DVB primaria...", }, { "Up/Dn for new location - OK to move", "Auf/Ab für neue Position - dann OK", "Gor/Dol za novo poz. - Ok za premik", + "Su/Giu per nuova posizione - OK per muovere", + }, + { "Editing process started", + "Schnitt gestartet", + "Urejanje se je zacelo", + "Processo di modifica iniziato", }, { NULL } }; diff --git a/interface.c b/interface.c index e5cc1d2..ef8368a 100644 --- a/interface.c +++ b/interface.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.c 1.32 2000/11/18 15:28:50 kls Exp $ + * $Id: interface.c 1.33 2000/12/09 11:04:10 kls Exp $ */ #include "interface.h" @@ -121,6 +121,12 @@ void cInterface::Fill(int x, int y, int w, int h, eDvbColor Color) cDvbApi::PrimaryDvbApi->Fill(x, y, w, h, Color); } +void cInterface::SetBitmap(int x, int y, const cBitmap &Bitmap) +{ + if (open) + cDvbApi::PrimaryDvbApi->SetBitmap(x, y, Bitmap); +} + void cInterface::Flush(void) { if (open) diff --git a/interface.h b/interface.h index cd46fba..50c3761 100644 --- a/interface.h +++ b/interface.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.h 1.20 2000/11/18 15:27:59 kls Exp $ + * $Id: interface.h 1.21 2000/12/09 10:48:41 kls Exp $ */ #ifndef __INTERFACE_H @@ -41,6 +41,7 @@ public: void Clear(void); void ClearEol(int x, int y, eDvbColor Color = clrBackground); void Fill(int x, int y, int w, int h, eDvbColor color = clrBackground); + void SetBitmap(int x, int y, const cBitmap &Bitmap); void Flush(void); void SetCols(int *c); eDvbFont SetFont(eDvbFont Font); @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.52 2000/11/18 16:30:13 kls Exp $ + * $Id: menu.c 1.58 2001/01/13 13:07:43 kls Exp $ */ #include "menu.h" @@ -669,7 +669,7 @@ eOSState cMenuChannels::Del(void) return osContinue; } } - if (Interface->Confirm(tr("Delete Channel?"))) { + if (Interface->Confirm(tr("Delete channel?"))) { // Move and renumber the channels: Channels.Del(channel); Channels.ReNumber(); @@ -1039,7 +1039,7 @@ eOSState cMenuTimers::Del(void) cTimer *ti = Timers.Get(Index); if (ti) { if (!ti->recording) { - if (Interface->Confirm(tr("Delete Timer?"))) { + if (Interface->Confirm(tr("Delete timer?"))) { Timers.Del(Timers.Get(Index)); cOsdMenu::Del(Index); Timers.Save(); @@ -1310,7 +1310,9 @@ private: cThreadLock threadLock; const cSchedules *schedules; bool now, next; + int otherChannel; eOSState Record(void); + eOSState Switch(void); void PrepareSchedule(cChannel *Channel); void PrepareWhatsOnNext(bool On); public: @@ -1322,6 +1324,7 @@ cMenuSchedule::cMenuSchedule(void) :cOsdMenu("", 6, 6) { now = next = false; + otherChannel = 0; cChannel *channel = Channels.GetByNumber(cDvbApi::CurrentChannel()); if (channel) { schedules = cDvbApi::PrimaryDvbApi->Schedules(&threadLock); @@ -1383,6 +1386,16 @@ eOSState cMenuSchedule::Record(void) return osContinue; } +eOSState cMenuSchedule::Switch(void) +{ + if (otherChannel) { + if (Channels.SwitchTo(otherChannel)) + return osEnd; + } + Interface->Error(tr("Can't switch channel!")); + return osContinue; +} + eOSState cMenuSchedule::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); @@ -1398,8 +1411,9 @@ eOSState cMenuSchedule::ProcessKey(eKeys Key) next = !next; return AddSubMenu(new cMenuWhatsOn(schedules, now)); case kYellow: return AddSubMenu(new cMenuWhatsOn(schedules, false)); + case kBlue: return Switch(); case kOk: if (Count()) - return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->eventInfo)); + return AddSubMenu(new cMenuEvent(((cMenuScheduleItem *)Get(Current()))->eventInfo, otherChannel)); break; default: break; } @@ -1411,6 +1425,10 @@ eOSState cMenuSchedule::ProcessKey(eKeys Key) cChannel *channel = Channels.GetByServiceID(ei->GetServiceID()); if (channel) { PrepareSchedule(channel); + if (channel->number != cDvbApi::CurrentChannel()) { + otherChannel = channel->number; + SetHelp(tr("Record"), tr("Now"), tr("Next"), tr("Switch")); + } Display(); } } @@ -1471,7 +1489,7 @@ eOSState cMenuRecordings::Del(void) if (ri) { //XXX what if this recording's file is currently in use??? //XXX if (!ti->recording) { - if (Interface->Confirm(tr("Delete Recording?"))) { + if (Interface->Confirm(tr("Delete recording?"))) { if (ri->recording->Delete()) { cReplayControl::ClearLastReplayed(ri->recording->FileName()); cOsdMenu::Del(Current()); @@ -1645,6 +1663,8 @@ cMenuMain::cMenuMain(bool Replaying) Add(new cOsdItem(buffer, osStopRecord)); delete buffer; } + if (cVideoCutter::Active()) + Add(new cOsdItem(tr("Cancel editing"), osCancelEdit)); SetHelp(tr("Record"), NULL, NULL, cReplayControl::LastReplayed() ? tr("Resume") : NULL); Display(); lastActivity = time(NULL); @@ -1661,13 +1681,19 @@ eOSState cMenuMain::ProcessKey(eKeys Key) case osRecordings: return AddSubMenu(new cMenuRecordings); case osSetup: return AddSubMenu(new cMenuSetup); case osCommands: return AddSubMenu(new cMenuCommands); - case osStopRecord: if (Interface->Confirm(tr("Stop Recording?"))) { + case osStopRecord: if (Interface->Confirm(tr("Stop recording?"))) { cOsdItem *item = Get(Current()); if (item) { cRecordControls::Stop(item->Text() + strlen(STOP_RECORDING)); return osEnd; } } + break; + case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) { + cVideoCutter::Stop(); + return osEnd; + } + break; default: switch (Key) { case kMenu: state = osEnd; break; case kRed: if (!HasSubMenu()) @@ -1726,23 +1752,21 @@ cDisplayChannel::~cDisplayChannel() void cDisplayChannel::DisplayChannel(const cChannel *Channel) { - if (!Interface->Recording()) { - if (Channel && Channel->number) - Interface->DisplayChannelNumber(Channel->number); - int BufSize = Width() + 1; - char buffer[BufSize]; - if (Channel && Channel->number) - snprintf(buffer, BufSize, "%d %s", Channel->number, Channel->name); - else - snprintf(buffer, BufSize, "%s", Channel ? Channel->name : tr("*** Invalid Channel ***")); - Interface->Fill(0, 0, MenuColumns, 1, clrBackground); - Interface->Write(0, 0, buffer); - time_t t = time(NULL); - struct tm *now = localtime(&t); - snprintf(buffer, BufSize, "%02d:%02d", now->tm_hour, now->tm_min); - Interface->Write(-5, 0, buffer); - Interface->Flush(); - } + if (Channel && Channel->number) + Interface->DisplayChannelNumber(Channel->number); + int BufSize = Width() + 1; + char buffer[BufSize]; + if (Channel && Channel->number) + snprintf(buffer, BufSize, "%d %s", Channel->number, Channel->name); + else + snprintf(buffer, BufSize, "%s", Channel ? Channel->name : tr("*** Invalid Channel ***")); + Interface->Fill(0, 0, MenuColumns, 1, clrBackground); + Interface->Write(0, 0, buffer); + time_t t = time(NULL); + struct tm *now = localtime(&t); + snprintf(buffer, BufSize, "%02d:%02d", now->tm_hour, now->tm_min); + Interface->Write(-5, 0, buffer); + Interface->Flush(); } void cDisplayChannel::DisplayInfo(void) @@ -1874,7 +1898,6 @@ cRecordControl::~cRecordControl() { Stop(true); delete instantId; - Interface->DisplayRecording(dvbApi->Index(), false); } void cRecordControl::Stop(bool KeepInstant) @@ -1890,6 +1913,7 @@ void cRecordControl::Stop(bool KeepInstant) Timers.Save(); } timer = NULL; + Interface->DisplayRecording(dvbApi->Index(), false); } } @@ -1946,7 +1970,7 @@ void cRecordControls::Stop(cDvbApi *DvbApi) if (RecordControls[i]) { if (RecordControls[i]->Uses(DvbApi)) { isyslog(LOG_INFO, "stopping recording on DVB device %d due to higher priority", DvbApi->Index() + 1); - RecordControls[i]->Stop(); + RecordControls[i]->Stop(true); } } } @@ -1975,6 +1999,50 @@ void cRecordControls::Process(void) } } +// --- cProgressBar ---------------------------------------------------------- + +class cProgressBar : public cBitmap { +protected: + int total; + int Pos(int p) { return p * width / total; } + void Mark(int x, bool Start, bool Current); +public: + cProgressBar(int Width, int Height, int Current, int Total, const cMarks &Marks); + }; + +cProgressBar::cProgressBar(int Width, int Height, int Current, int Total, const cMarks &Marks) +:cBitmap(Width, Height) +{ + total = Total; + if (total > 0) { + int p = Pos(Current); + Fill(0, 0, p, Height - 1, clrGreen); + Fill(p + 1, 0, Width - 1, Height - 1, clrWhite); + bool Start = true; + for (const cMark *m = Marks.First(); m; m = Marks.Next(m)) { + int p1 = Pos(m->position); + if (Start) { + const cMark *m2 = Marks.Next(m); + int p2 = Pos(m2 ? m2->position : total); + int h = Height / 3; + Fill(p1, h, p2, Height - h, clrRed); + } + Mark(p1, Start, m->position == Current); + Start = !Start; + } + } +} + +void cProgressBar::Mark(int x, bool Start, bool Current) +{ + Fill(x, 0, x, height - 1, clrBlack); + const int d = height / (Current ? 3 : 9); + for (int i = 0; i < d; i++) { + int h = Start ? i : height - 1 - i; + Fill(x - d + i, h, x + d - i, h, Current ? clrRed : clrBlack); + } +} + // --- cReplayControl -------------------------------------------------------- char *cReplayControl::fileName = NULL; @@ -1982,16 +2050,18 @@ char *cReplayControl::title = NULL; cReplayControl::cReplayControl(void) { - dvbApi = cDvbApi::PrimaryDvbApi;//XXX - visible = shown = false; - if (fileName) - dvbApi->StartReplay(fileName, title); + dvbApi = cDvbApi::PrimaryDvbApi; + visible = shown = displayFrames = false; + if (fileName) { + marks.Load(fileName); + dvbApi->StartReplay(fileName); + } } cReplayControl::~cReplayControl() { Hide(); - dvbApi->Stop(); + dvbApi->StopReplay(); } void cReplayControl::SetRecording(const char *FileName, const char *Title) @@ -2020,7 +2090,7 @@ void cReplayControl::Show(void) if (!visible) { Interface->Open(MenuColumns, -3); needsFastResponse = visible = true; - shown = dvbApi->ShowProgress(true); + shown = ShowProgress(true); } } @@ -2032,27 +2102,148 @@ void cReplayControl::Hide(void) } } +bool cReplayControl::ShowProgress(bool Initial) +{ + int Current, Total; + + if (dvbApi->GetIndex(Current, Total) && Total > 0) { + if (Initial) { + Interface->Clear(); + if (title) + Interface->Write(0, 0, title); + displayFrames = marks.Count() > 0; + } + Interface->Write(-7, 2, IndexToHMSF(Total)); + Interface->Flush(); +#ifdef DEBUG_OSD + int p = Width() * Current / Total; + Interface->Fill(0, 1, p, 1, clrGreen); + Interface->Fill(p, 1, Width() - p, 1, clrWhite); +#else + cProgressBar ProgressBar(Width() * dvbApi->CellWidth(), dvbApi->LineHeight(), Current, Total, marks); + Interface->SetBitmap(0, dvbApi->LineHeight(), ProgressBar); + Interface->Flush(); +#endif + Interface->Write(0, 2, IndexToHMSF(Current, displayFrames)); + Interface->Flush(); + return true; + } + return false; +} + +void cReplayControl::MarkToggle(void) +{ + int Current, Total; + if (dvbApi->GetIndex(Current, Total, true)) { + cMark *m = marks.Get(Current); + if (m) + marks.Del(m); + else + marks.Add(Current); + marks.Save(); + } + displayFrames = marks.Count() > 0; + if (!displayFrames) + Interface->Fill(0, 2, Width() / 2, 1, clrBackground); +} + +void cReplayControl::MarkJump(bool Forward) +{ + int Current, Total; + if (dvbApi->GetIndex(Current, Total)) { + cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current); + if (m) + dvbApi->Goto(m->position, true); + } +} + +void cReplayControl::MarkMove(bool Forward) +{ + int Current, Total; + if (dvbApi->GetIndex(Current, Total)) { + cMark *m = marks.Get(Current); + if (m) { + int p = dvbApi->SkipFrames(Forward ? 1 : -1); + cMark *m2; + if (Forward) { + if ((m2 = marks.Next(m)) != NULL && m2->position <= p) + return; + } + else { + if ((m2 = marks.Prev(m)) != NULL && m2->position >= p) + return; + } + dvbApi->Goto(m->position = p, true); + marks.Save(); + } + } +} + +void cReplayControl::EditCut(void) +{ + Hide(); + if (!cVideoCutter::Active()) { + if (!cVideoCutter::Start(fileName)) + Interface->Error(tr("Can't start editing process!")); + else + Interface->Info(tr("Editing process started")); + } + else + Interface->Error(tr("Editing process already active!")); +} + +void cReplayControl::EditTest(void) +{ + int Current, Total; + if (dvbApi->GetIndex(Current, Total)) { + cMark *m = marks.Get(Current); + if (!m) + m = marks.GetNext(Current); + if (m) { + if ((m->Index() & 0x01) != 0) + m = marks.Next(m); + if (m) { + dvbApi->Goto(m->position - dvbApi->SecondsToFrames(3)); + dvbApi->Play(); + } + } + } +} + eOSState cReplayControl::ProcessKey(eKeys Key) { if (!dvbApi->Replaying()) return osEnd; if (visible) - shown = dvbApi->ShowProgress(!shown) || shown; + shown = ShowProgress(!shown) || shown; switch (Key) { + // Positioning: case kUp: dvbApi->Play(); break; case kDown: dvbApi->Pause(); break; - case kBlue: Hide(); - dvbApi->Stop(); - return osEnd; case kLeft: dvbApi->Backward(); break; case kRight: dvbApi->Forward(); break; case kLeft|k_Release: case kRight|k_Release: dvbApi->Play(); break; case kGreen|k_Repeat: - case kGreen: dvbApi->Skip(-60); break; + case kGreen: dvbApi->SkipSeconds(-60); break; case kYellow|k_Repeat: - case kYellow: dvbApi->Skip(60); break; + case kYellow: dvbApi->SkipSeconds(60); break; + case kBlue: Hide(); + dvbApi->StopReplay(); + return osEnd; + // Editing: + //XXX should we do this only when the ProgressDisplay is on??? + case kMarkToggle: MarkToggle(); break; + case kMarkJumpBack: MarkJump(false); break; + case kMarkJumpForward: MarkJump(true); break; + case kMarkMoveBack|k_Repeat: + case kMarkMoveBack: MarkMove(false); break; + case kMarkMoveForward|k_Repeat: + case kMarkMoveForward: MarkMove(true); break; + case kEditCut: EditCut(); break; + case kEditTest: EditTest(); break; + // Menu control: case kMenu: Hide(); return osMenu; // allow direct switching to menu case kOk: visible ? Hide() : Show(); break; case kBack: return osRecordings; @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.h 1.14 2000/11/12 12:33:00 kls Exp $ + * $Id: menu.h 1.16 2000/12/25 14:25:29 kls Exp $ */ #ifndef _MENU_H @@ -79,11 +79,18 @@ public: class cReplayControl : public cOsdBase { private: cDvbApi *dvbApi; - bool visible, shown; + cMarks marks; + bool visible, shown, displayFrames; void Show(void); void Hide(void); static char *fileName; static char *title; + bool ShowProgress(bool Initial); + void MarkToggle(void); + void MarkJump(bool Forward); + void MarkMove(bool Forward); + void EditCut(void); + void EditTest(void); public: cReplayControl(void); virtual ~cReplayControl(); @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.h 1.17 2000/11/12 15:27:34 kls Exp $ + * $Id: osd.h 1.18 2000/12/24 10:16:52 kls Exp $ */ #ifndef __OSD_H @@ -29,6 +29,7 @@ enum eOSState { osUnknown, osReplay, osStopRecord, osStopReplay, + osCancelEdit, osSwitchDvb, osBack, osEnd, diff --git a/recording.c b/recording.c index f45be96..064731d 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 1.21 2000/11/18 16:22:29 kls Exp $ + * $Id: recording.c 1.24 2001/01/13 12:17:15 kls Exp $ */ #define _GNU_SOURCE @@ -26,6 +26,7 @@ #define NAMEFORMAT "%s/%s/" DATAFORMAT #define SUMMARYFILESUFFIX "/summary.vdr" +#define MARKSFILESUFFIX "/marks.vdr" #define FINDCMD "find %s -follow -type d -name '%s' 2> /dev/null | sort -df" @@ -125,6 +126,7 @@ cRecording::cRecording(const char *FileName) strncpy(name, FileName, p - FileName); name[p - FileName] = 0; strreplace(name, '_', ' '); + strreplace(name, '\x01', '\''); } // read an optional summary file: char *SummaryFileName = NULL; @@ -175,8 +177,10 @@ const char *cRecording::FileName(void) if (!fileName) { struct tm *t = localtime(&start); asprintf(&fileName, NAMEFORMAT, VideoDirectory, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime); - if (fileName) + if (fileName) { strreplace(fileName, ' ', '_'); + strreplace(fileName, '\'', '\x01'); + } } return fileName; } @@ -269,3 +273,107 @@ bool cRecordings::Load(bool Deleted) return result; } +// --- cMark ----------------------------------------------------------------- + +char *cMark::buffer = NULL; + +cMark::cMark(int Position, const char *Comment) +{ + position = Position; + comment = Comment ? strdup(Comment) : NULL; +} + +cMark::~cMark() +{ + delete comment; +} + +const char *cMark::ToText(void) +{ + delete buffer; + asprintf(&buffer, "%s%s%s\n", IndexToHMSF(position, true), comment ? " " : "", comment ? comment : ""); + return buffer; +} + +bool cMark::Parse(const char *s) +{ + delete comment; + comment = NULL; + position = HMSFToIndex(s); + const char *p = strchr(s, ' '); + if (p) { + p = skipspace(p); + if (*p) { + comment = strdup(p); + comment[strlen(comment) - 1] = 0; // strips trailing newline + } + } + return true; +} + +bool cMark::Save(FILE *f) +{ + return fprintf(f, ToText()) > 0; +} + +// --- cMarks ---------------------------------------------------------------- + +bool cMarks::Load(const char *RecordingFileName) +{ + const char *MarksFile = AddDirectory(RecordingFileName, MARKSFILESUFFIX); + if (cConfig<cMark>::Load(MarksFile)) { + Sort(); + return true; + } + return false; +} + +void cMarks::Sort(void) +{ + for (cMark *m1 = First(); m1; m1 = Next(m1)) { + for (cMark *m2 = Next(m1); m2; m2 = Next(m2)) { + if (m2->position < m1->position) { + swap(m1->position, m2->position); + swap(m1->comment, m2->comment); + } + } + } +} + +cMark *cMarks::Add(int Position) +{ + cMark *m = Get(Position); + if (!m) { + cConfig<cMark>::Add(m = new cMark(Position)); + Sort(); + } + return m; +} + +cMark *cMarks::Get(int Position) +{ + for (cMark *mi = First(); mi; mi = Next(mi)) { + if (mi->position == Position) + return mi; + } + return NULL; +} + +cMark *cMarks::GetPrev(int Position) +{ + for (cMark *mi = Last(); mi; mi = Prev(mi)) { + if (mi->position < Position) + return mi; + } + return NULL; +} + +cMark *cMarks::GetNext(int Position) +{ + for (cMark *mi = First(); mi; mi = Next(mi)) { + if (mi->position > Position) + return mi; + } + return NULL; +} + diff --git a/recording.h b/recording.h index 7511c65..454c356 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.h 1.10 2000/10/03 12:27:49 kls Exp $ + * $Id: recording.h 1.11 2000/12/16 14:25:20 kls Exp $ */ #ifndef __RECORDING_H @@ -47,4 +47,27 @@ public: bool Load(bool Deleted = false); }; +class cMark : public cListObject { +private: + static char *buffer; +public: + int position; + char *comment; + cMark(int Position = 0, const char *Comment = NULL); + ~cMark(); + const char *ToText(void); + bool Parse(const char *s); + bool Save(FILE *f); + }; + +class cMarks : public cConfig<cMark> { +public: + bool Load(const char *RecordingFileName); + void Sort(void); + cMark *Add(int Position); + cMark *Get(int Position); + cMark *GetPrev(int Position); + cMark *GetNext(int Position); + }; + #endif //__RECORDING_H @@ -6,7 +6,7 @@ * * Ported to LIRC by Carsten Koch <Carsten.Koch@icem.de> 2000-06-16. * - * $Id: remote.c 1.19 2000/11/11 11:22:22 kls Exp $ + * $Id: remote.c 1.20 2000/12/03 11:55:06 kls Exp $ */ #include "remote.h" @@ -115,7 +115,7 @@ cRcIoRCU::cRcIoRCU(char *DeviceName) cRcIoRCU::~cRcIoRCU() { - Stop(); + Cancel(); } void cRcIoRCU::Action(void) @@ -420,7 +420,7 @@ cRcIoLIRC::cRcIoLIRC(char *DeviceName) cRcIoLIRC::~cRcIoLIRC() { - Stop(); + Cancel(); } void cRcIoLIRC::Action(void) @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 1.12 2000/11/05 13:44:42 kls Exp $ + * $Id: svdrp.c 1.13 2000/12/03 15:34:35 kls Exp $ */ #define _GNU_SOURCE @@ -18,6 +18,7 @@ #include "svdrp.h" #include <arpa/inet.h> #include <ctype.h> +#include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <stdarg.h> @@ -4,12 +4,15 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.c 1.4 2000/11/14 18:38:25 kls Exp $ + * $Id: thread.c 1.7 2000/12/24 12:27:21 kls Exp $ */ #include "thread.h" +#include <errno.h> #include <signal.h> +#include <sys/wait.h> #include <unistd.h> +#include "tools.h" // --- cThread --------------------------------------------------------------- @@ -25,7 +28,7 @@ cThread::cThread(void) signalHandlerInstalled = true; } running = false; - parentPid = lockingPid = 0; + parentPid = threadPid = lockingPid = 0; locked = 0; } @@ -40,6 +43,7 @@ void cThread::SignalHandler(int signum) void *cThread::StartThread(cThread *Thread) { + Thread->threadPid = getpid(); Thread->Action(); return NULL; } @@ -49,13 +53,37 @@ bool cThread::Start(void) if (!running) { running = true; parentPid = getpid(); - pthread_create(&thread, NULL, &StartThread, (void *)this); + pthread_create(&thread, NULL, (void *(*) (void *))&StartThread, (void *)this); + usleep(10000); // otherwise calling Active() immediately after Start() causes a "pure virtual method called" error } return true; //XXX return value of pthread_create()??? } -void cThread::Stop(void) +bool cThread::Active(void) { + if (threadPid) { + if (kill(threadPid, SIGIO) < 0) { // couldn't find another way of checking whether the thread is still running - any ideas? + if (errno == ESRCH) + threadPid = 0; + else + LOG_ERROR; + } + else + return true; + } + return false; +} + +void cThread::Cancel(int WaitSeconds) +{ + if (WaitSeconds > 0) { + for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) { + if (!Active()) + return; + usleep(10000); + } + esyslog(LOG_ERR, "ERROR: thread %d won't end (waited %d seconds) - cancelling it...", threadPid, WaitSeconds); + } pthread_cancel(thread); } @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.h 1.3 2000/11/14 18:38:11 kls Exp $ + * $Id: thread.h 1.4 2000/12/03 11:18:37 kls Exp $ */ #ifndef __THREAD_H @@ -28,7 +28,7 @@ class cThread { private: pthread_t thread; cMutex Mutex; - pid_t parentPid, lockingPid; + pid_t parentPid, threadPid, lockingPid; int locked; bool running; static bool signalHandlerInstalled; @@ -39,11 +39,12 @@ private: protected: void WakeUp(void); virtual void Action(void) = 0; - void Stop(void); + void Cancel(int WaitSeconds = 0); public: cThread(void); virtual ~cThread(); bool Start(void); + bool Active(void); }; // cThreadLock can be used to easily set a lock in a thread and make absolutely diff --git a/timers.conf b/timers.conf deleted file mode 100644 index 05946a5..0000000 --- a/timers.conf +++ /dev/null @@ -1,14 +0,0 @@ -1:15:M------:2128:2205:80:7:Neues: -1:3:-T-----:2013:2125:99:99:SevenDays: -1:10:-T-----:2058:2202:99:10:Quarks: -1:25:-T-----:2305:0020:99:99:UFO: -1:14:--W----:1920:2020:70:99:Rettungsflieger: -0:2:--W----:2110:2325:99:99:BulleVonToelz: -1:3:---T---:2210:2315:50:20:IngoAppelt: -1:2:----F--:2013:2125:99:99:Farscape: -1:1:----F--:2215:2325:50:20:7Tage7Koepfe: -0:11:-----S-:2058:2135:99:99:Computer: -0:2:-----S-:2220:2340:99:30:Wochenshow: -1:11:------S:2013:2035:99:10:Centauri: -1:15:MTWTF--:1828:1901:10:5:nano: -1:1:MTWTF--:1553:1710:99:99:Hammerman: @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.23 2000/11/11 15:17:12 kls Exp $ + * $Id: tools.c 1.27 2001/01/13 15:35:02 kls Exp $ */ #define _GNU_SOURCE @@ -15,10 +15,7 @@ #if defined(DEBUG_OSD) #include <ncurses.h> #endif -#include <signal.h> -#include <stdlib.h> #include <sys/time.h> -#include <sys/wait.h> #include <unistd.h> #define MaxBuffer 1000 @@ -30,29 +27,6 @@ void writechar(int filedes, char c) write(filedes, &c, sizeof(c)); } -void writeint(int filedes, int n) -{ - write(filedes, &n, sizeof(n)); -} - -char readchar(int filedes) -{ - char c; - read(filedes, &c, 1); - return c; -} - -bool readint(int filedes, int &n) -{ - return cFile::AnyFileReady(filedes, 0) && read(filedes, &n, sizeof(n)) == sizeof(n); -} - -void purge(int filedes) -{ - while (cFile::AnyFileReady(filedes, 0)) - readchar(filedes); -} - char *readline(FILE *f) { static char buffer[MaxBuffer]; @@ -146,7 +120,7 @@ const char *AddDirectory(const char *DirName, const char *FileName) return buf; } -#define DFCMD "df -m %s" +#define DFCMD "df -m '%s'" uint FreeDiskSpaceMB(const char *Directory) { @@ -205,7 +179,7 @@ bool MakeDirs(const char *FileName, bool IsDirectory) if (stat(s, &fs) != 0 || !S_ISDIR(fs.st_mode)) { dsyslog(LOG_INFO, "creating directory %s", s); if (mkdir(s, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) { - esyslog(LOG_ERR, "ERROR: %s: %s", s, strerror(errno)); + LOG_ERROR_STR(s); result = false; break; } @@ -266,44 +240,32 @@ bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks) if (remove(FileName) == 0) return true; } - else + else if (errno != ENOENT) { LOG_ERROR_STR(FileName); - return false; -} - -bool CheckProcess(pid_t pid) -{ - pid_t Pid2Check = pid; - int status; - pid = waitpid(Pid2Check, &status, WNOHANG); - if (pid < 0) { - if (errno != ECHILD) - LOG_ERROR; return false; } return true; } -void KillProcess(pid_t pid, int Timeout) +char *ReadLink(const char *FileName) { - pid_t Pid2Wait4 = pid; - for (time_t t0 = time(NULL); time(NULL) - t0 < Timeout; ) { - int status; - pid_t pid = waitpid(Pid2Wait4, &status, WNOHANG); - if (pid < 0) { - if (errno != ECHILD) - LOG_ERROR; - return; - } - if (pid == Pid2Wait4) - return; - } - esyslog(LOG_ERR, "ERROR: process %d won't end (waited %d seconds) - terminating it...", Pid2Wait4, Timeout); - if (kill(Pid2Wait4, SIGTERM) < 0) { - esyslog(LOG_ERR, "ERROR: process %d won't terminate (%s) - killing it...", Pid2Wait4, strerror(errno)); - if (kill(Pid2Wait4, SIGKILL) < 0) - esyslog(LOG_ERR, "ERROR: process %d won't die (%s) - giving up", Pid2Wait4, strerror(errno)); + char RealName[_POSIX_PATH_MAX]; + const char *TargetName = NULL; + int n = readlink(FileName, RealName, sizeof(RealName) - 1); + if (n < 0) { + if (errno == ENOENT || errno == EINVAL) // file doesn't exist or is not a symlink + TargetName = FileName; + else { // some other error occurred + LOG_ERROR_STR(FileName); + } + } + else if (n < int(sizeof(RealName))) { // got it! + RealName[n] = 0; + TargetName = RealName; } + else + esyslog(LOG_ERR, "ERROR: symlink's target name too long: %s", FileName); + return TargetName ? strdup(TargetName) : NULL; } // --- cFile ----------------------------------------------------------------- @@ -426,6 +388,45 @@ bool cFile::FileReady(int FileDes, int TimeoutMs) return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set); } +// --- cSafeFile ------------------------------------------------------------- + +cSafeFile::cSafeFile(const char *FileName) +{ + f = NULL; + fileName = ReadLink(FileName); + tempName = fileName ? new char[strlen(fileName) + 5] : NULL; + if (tempName) + strcat(strcpy(tempName, fileName), ".$$$"); +} + +cSafeFile::~cSafeFile() +{ + if (f) + fclose(f); + unlink(tempName); + delete fileName; + delete tempName; +} + +bool cSafeFile::Open(void) +{ + if (!f && fileName && tempName) { + f = fopen(tempName, "w"); + if (!f) + LOG_ERROR_STR(tempName); + } + return f != NULL; +} + +void cSafeFile::Close(void) +{ + if (f) { + fclose(f); + f = NULL; + rename(tempName, fileName); + } +} + // --- cListObject ----------------------------------------------------------- cListObject::cListObject(void) @@ -4,13 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.20 2000/11/12 15:27:06 kls Exp $ + * $Id: tools.h 1.23 2001/01/13 15:36:00 kls Exp $ */ #ifndef __TOOLS_H #define __TOOLS_H -#include <errno.h> +//#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> @@ -24,19 +24,16 @@ extern int SysLogLevel; #define isyslog if (SysLogLevel > 1) syslog #define dsyslog if (SysLogLevel > 2) syslog -#define LOG_ERROR esyslog(LOG_ERR, "ERROR (%s,%d): %s", __FILE__, __LINE__, strerror(errno)) -#define LOG_ERROR_STR(s) esyslog(LOG_ERR, "ERROR: %s: %s", s, strerror(errno)); +#define LOG_ERROR esyslog(LOG_ERR, "ERROR (%s,%d): %m", __FILE__, __LINE__) +#define LOG_ERROR_STR(s) esyslog(LOG_ERR, "ERROR: %s: %m", s) #define SECSINDAY 86400 -#define MAXPROCESSTIMEOUT 3 // seconds #define DELETENULL(p) (delete (p), p = NULL) +template<class T> inline void swap(T &a, T &b) { T t = a; a = b; b = t; }; + void writechar(int filedes, char c); -void writeint(int filedes, int n); -char readchar(int filedes); -bool readint(int filedes, int &n); -void purge(int filedes); char *readline(FILE *f); char *strn0cpy(char *dest, const char *src, size_t n); char *strreplace(char *s, char c1, char c2); @@ -51,8 +48,7 @@ uint FreeDiskSpaceMB(const char *Directory); bool DirectoryOk(const char *DirName, bool LogErrors = false); bool MakeDirs(const char *FileName, bool IsDirectory = false); bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks = false); -bool CheckProcess(pid_t pid); -void KillProcess(pid_t pid, int Timeout = MAXPROCESSTIMEOUT); +char *ReadLink(const char *FileName); class cFile { private: @@ -73,6 +69,19 @@ public: static bool FileReady(int FileDes, int TimeoutMs = 1000); }; +class cSafeFile { +private: + FILE *f; + char *fileName; + char *tempName; +public: + cSafeFile(const char *FileName); + ~cSafeFile(); + operator FILE* () { return f; } + bool Open(void); + void Close(void); + }; + class cListObject { private: cListObject *prev, *next; @@ -105,6 +114,8 @@ template<class T> class cList : public cListBase { public: T *Get(int Index) const { return (T *)cListBase::Get(Index); } T *First(void) const { return (T *)objects; } + T *Last(void) const { return (T *)lastObject; } + T *Prev(const T *object) const { return (T *)object->Prev(); } T *Next(const T *object) const { return (T *)object->Next(); } }; @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/people/kls/vdr * - * $Id: vdr.c 1.46 2000/11/18 13:46:56 kls Exp $ + * $Id: vdr.c 1.49 2001/01/14 15:29:51 kls Exp $ */ #include <getopt.h> @@ -141,8 +141,8 @@ int main(int argc, char *argv[]) #if !defined(DEBUG_OSD) && !defined(REMOTE_KBD) pid_t pid = fork(); if (pid < 0) { - fprintf(stderr, "%s\n", strerror(errno)); - esyslog(LOG_ERR, "ERROR: %s", strerror(errno)); + fprintf(stderr, "%m\n"); + esyslog(LOG_ERR, "ERROR: %m"); abort(); } if (pid != 0) @@ -179,7 +179,7 @@ int main(int argc, char *argv[]) cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB); - Channels.SwitchTo(1); + Channels.SwitchTo(Setup.CurrentChannel); cEITScanner EITScanner; @@ -306,10 +306,15 @@ int main(int argc, char *argv[]) default: break; } } - if (!Menu) + if (!Menu) { EITScanner.Process(); + cVideoCutter::Active(); + } } isyslog(LOG_INFO, "caught signal %d", Interrupted); + Setup.CurrentChannel = cDvbApi::CurrentChannel(); + Setup.Save(); + cVideoCutter::Stop(); delete Menu; delete ReplayControl; delete Interface; @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: videodir.c 1.2 2000/09/15 13:23:47 kls Exp $ + * $Id: videodir.c 1.3 2000/12/24 12:51:41 kls Exp $ */ #include "videodir.h" @@ -180,3 +180,20 @@ bool VideoFileSpaceAvailable(unsigned int SizeMB) } return Dir.FreeMB() >= SizeMB; } + +const char *PrefixVideoFileName(const char *FileName, char Prefix) +{ + static char *PrefixedName = NULL; + + if (!PrefixedName || strlen(PrefixedName) <= strlen(FileName)) + PrefixedName = (char *)realloc(PrefixedName, strlen(FileName) + 2); + if (PrefixedName) { + strcpy(PrefixedName, VideoDirectory); + char *p = PrefixedName + strlen(PrefixedName); + *p++ = '/'; + *p++ = Prefix; + strcpy(p, FileName + strlen(VideoDirectory) + 1); + } + return PrefixedName; +} + @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: videodir.h 1.1 2000/07/29 14:08:27 kls Exp $ + * $Id: videodir.h 1.2 2000/12/24 12:41:10 kls Exp $ */ #ifndef __VIDEODIR_H @@ -17,5 +17,6 @@ int CloseVideoFile(int FileHandle); bool RenameVideoFile(const char *OldName, const char *NewName); bool RemoveVideoFile(const char *FileName); bool VideoFileSpaceAvailable(unsigned int SizeMB); +const char *PrefixVideoFileName(const char *FileName, char Prefix); #endif //__VIDEODIR_H |