summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKlaus Schmidinger <kls (at) cadsoft (dot) de>2000-07-25 18:00:00 +0200
committerKlaus Schmidinger <kls (at) cadsoft (dot) de>2000-07-25 18:00:00 +0200
commit1d22145c423f2524c7766b4ae30ee2c26174113d (patch)
tree57d5ead142972730c932a7d94c375e99f9e743a9
parent3b78ec8374aac8daa560fa0cd06260fca6eb1500 (diff)
downloadvdr-patch-lnbsharing-1d22145c423f2524c7766b4ae30ee2c26174113d.tar.gz
vdr-patch-lnbsharing-1d22145c423f2524c7766b4ae30ee2c26174113d.tar.bz2
Version 0.6vdr-0.6
- Added support for LIRC remote control (thanks to Carsten Koch!). There are now three different remote control modes: KBD (PC-Keyboard), RCU and LIRC. See the INSTALL file for information on how to enable either of these modes. The default mode is now KBD, not RCU as before (to make it work immediately even if there is no actual remote control). - Fixed small bug in dvbapi.c that was causing some channels (many on hotbird) not to be correctly tuned (thanks to Plamen Ganev!). - Now clearing the replay buffer in search forward/back, which results in faster reaction. - The 'Recordings' menu is now listed alphabetically (thanks to Carsten Koch!). - The new 'epg2timers' tool (thanks to Carsten Koch!) can be used to convert an EPG "merkliste" page (http://www.tvtv.de) to vdr timer entries. - The new 'xtvrc2vdr' tool (thanks to Plamen Ganev!) can be used to convert 'xtvrc' channel files into 'vdr' format. - When more than one timer matches at the same time, the first one in the list with the highest 'Priority' is selected. - The MANUAL section on "Programming the Timer" has been filled in. - The year in the "Recordings" menu as well as in the progress display during replay has been reduced to 2 digits to allow more space for the recording's title. In the internal file structure the year is still stored with 4 digits, so there will be no problem at the next turn of the century ;-) - Channel names and timer filenames can now contain blanks. To avoid problems with file names that contain blanks, all blanks in recording file names are converted to underscores. - The polarization can now be given in uppercase or lowercase characters in channels.conf. - Fixed buffer initialization to work with DVB driver version 0.6. - Implemented the "Simple Video Disk Recorder Protocol" (SVDRP) to control the VDR over a network connection. - Implemented command line option handling. - The program can now run in full background mode by using the --daemon option. - Added a "summary" field to the timers (thanks to Carsten Koch!). This field can contain a descriptive text of the programme and will be displayed when the "Blue" key is pressed on a recording that was created by this timer. If the text contains the special character '|', a newline will be inserted at that place. When pressing "Ok" on a timer that contains a summary field, the summary will be displayed. To edit such a timer the "Red" key must be pressed. Timers without a summary still go into Edit mode when pressing "Ok". The summary field can only be filled in directly by editing the 'timers.conf' file with a text editor, or by defining/modifying the timer via the SVDRP interface.
-rw-r--r--BUGS6
-rw-r--r--CONTRIBUTORS6
-rw-r--r--HISTORY39
-rw-r--r--INSTALL11
-rw-r--r--MANUAL47
-rw-r--r--Makefile9
-rw-r--r--TODO1
-rw-r--r--Tools/epg2timers/epg2timers.cxx242
-rw-r--r--Tools/xtvrc2vdr/Makefile16
-rw-r--r--Tools/xtvrc2vdr/hotbird.conf191
-rw-r--r--Tools/xtvrc2vdr/xtvrc.hotbird1337
-rw-r--r--Tools/xtvrc2vdr/xtvrc2vdr.c146
-rw-r--r--channels.conf5
-rw-r--r--config.c101
-rw-r--r--config.h29
-rw-r--r--dvbapi.c25
-rw-r--r--menu.c92
-rw-r--r--recording.c81
-rw-r--r--recording.h6
-rw-r--r--remote.c12
-rw-r--r--remote.h6
-rw-r--r--svdrp.c620
-rw-r--r--svdrp.h52
-rw-r--r--timers.conf19
-rw-r--r--tools.c52
-rw-r--r--tools.h6
-rw-r--r--vdr.c84
27 files changed, 3138 insertions, 103 deletions
diff --git a/BUGS b/BUGS
index d9e2687..cd96197 100644
--- a/BUGS
+++ b/BUGS
@@ -4,9 +4,3 @@ Video Disk Recorder - Known Bugs
* Sometimes the picture "jumps" as if a frame is skipped.
Presumably this is a problem in the card driver or firmware?
-* When the on-screen display is activated during recording,
- the video data stream gets corrupted, which results in a
- distorted picture when replaying such a recording.
- I assume this is a problem in the driver of firmware.
- There is no such problem in replay mode.
-
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index fbf6e86..0e2794f 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -2,4 +2,10 @@ Thanks go to the following people for patches and contributions:
Carsten Koch <Carsten.Koch@icem.de>
for adding LIRC support
+ for making the 'Recordings' menu be listed alphabetically
+ for implementing the 'Summary' feature
+ for adding the 'epg2timers' tool (see Tools/epg2timers)
+Plamen Ganev <pganev@com-it.net>
+ for fixing the frequency offset for Hotbird channels
+ for adding the 'xtvrc2vdr' tool (see Tools/xtvrc2vdr)
diff --git a/HISTORY b/HISTORY
index 40150c6..765f4d1 100644
--- a/HISTORY
+++ b/HISTORY
@@ -56,11 +56,46 @@ Video Disk Recorder Revision History
the PC keyboard to better resemble the "up-down-left-right-ok" layout on
menu controlling remote control units.
-2000-07-15: Version 0.06
+2000-07-25: Version 0.6
- Added support for LIRC remote control (thanks to Carsten Koch!).
There are now three different remote control modes: KBD (PC-Keyboard), RCU
and LIRC. See the INSTALL file for information on how to enable either of
these modes. The default mode is now KBD, not RCU as before (to make it
work immediately even if there is no actual remote control).
-
+- Fixed small bug in dvbapi.c that was causing some channels (many on hotbird)
+ not to be correctly tuned (thanks to Plamen Ganev!).
+- Now clearing the replay buffer in search forward/back, which results in
+ faster reaction.
+- The 'Recordings' menu is now listed alphabetically (thanks to Carsten Koch!).
+- The new 'epg2timers' tool (thanks to Carsten Koch!) can be used to convert
+ an EPG "merkliste" page (http://www.tvtv.de) to vdr timer entries.
+- The new 'xtvrc2vdr' tool (thanks to Plamen Ganev!) can be used to convert
+ 'xtvrc' channel files into 'vdr' format.
+- When more than one timer matches at the same time, the first one in the list
+ with the highest 'Priority' is selected.
+- The MANUAL section on "Programming the Timer" has been filled in.
+- The year in the "Recordings" menu as well as in the progress display during
+ replay has been reduced to 2 digits to allow more space for the recording's
+ title. In the internal file structure the year is still stored with 4 digits,
+ so there will be no problem at the next turn of the century ;-)
+- Channel names and timer filenames can now contain blanks. To avoid problems
+ with file names that contain blanks, all blanks in recording file names are
+ converted to underscores.
+- The polarization can now be given in uppercase or lowercase characters in
+ channels.conf.
+- Fixed buffer initialization to work with DVB driver version 0.6.
+- Implemented the "Simple Video Disk Recorder Protocol" (SVDRP) to control
+ the VDR over a network connection.
+- Implemented command line option handling.
+- The program can now run in full background mode by using the --daemon option.
+- Added a "summary" field to the timers (thanks to Carsten Koch!).
+ This field can contain a descriptive text of the programme and will be
+ displayed when the "Blue" key is pressed on a recording that was created by
+ this timer. If the text contains the special character '|', a newline will
+ be inserted at that place. When pressing "Ok" on a timer that contains a
+ summary field, the summary will be displayed. To edit such a timer the "Red"
+ key must be pressed. Timers without a summary still go into Edit mode when
+ pressing "Ok". The summary field can only be filled in directly by editing
+ the 'timers.conf' file with a text editor, or by defining/modifying the timer
+ via the SVDRP interface.
diff --git a/INSTALL b/INSTALL
index 1a6442d..3261ef0 100644
--- a/INSTALL
+++ b/INSTALL
@@ -40,6 +40,17 @@ When running, the 'vdr' program writes status information into the
system log file (/var/log/messages). You may want to watch these
messages (tail -f /var/log/mesages) to see if there are any problems.
+The program can be controlled via a network connection to its SVDRP
+port ("Simple Video Disk Recorder Protocol"). By default, it listens
+on port 2001 (use the --port=PORT option to change this). For details
+about the SVDRP syntax see the source file 'svdrp.c'.
+
+If the program shall run as a daemon, use the --daemon option. This
+will completely detach it from the terminal and will continue as a
+background process.
+
+Use "vdr --help" for a list of available command line options.
+
The video data directory:
-------------------------
diff --git a/MANUAL b/MANUAL
index cea3946..c9b71a3 100644
--- a/MANUAL
+++ b/MANUAL
@@ -41,7 +41,11 @@ Video Disk Recorder User's Manual
by pressing the "Right" button (which puts brackets around the current
character as in "[R]TL"), selecting the desired character position with
"Left" and "Right", and changing the character with the "Up" and "Down"
- keys. "Ok" then confirms the changes.
+ keys. "Ok" then confirms the changes. The special character '^' can be used
+ to "cut off" a string at this position. When this character is visible in the
+ brackets (as in abc[^]), the next press to the "Left" or "Ok" button will
+ actually cut off the string. Using "Up" and/or "Down" brings back the
+ original rest of the string (unless you have pressed "Left" or "Ok").
The "Red", "Green", "Yellow" and "Blue" buttons have special meanings
in various menus and are listed at the bottom of the on-screen-display.
@@ -108,4 +112,43 @@ Video Disk Recorder User's Manual
* Programming the Timer
Use the "Timer" menu to maintain your list of timer controlled recordings.
-
+ The parameters in the "Edit Timer" menu have the following meanings:
+
+ Active: Defines whether the timer will be processed (set it to 'no' to
+ temporarily desable a timer).
+ Channel: The channel to be recorded (as defined in the "Channels" list).
+ Any changes made in the "Channels" list (like renaming or
+ reordering channels) will be automatically reflected in the
+ timers settings.
+ Day: The day on which this timer shall start. This can be either a
+ "day of month" (1..31), which allows programming a "single shot"
+ timer that hits once and is deleted after it ends. Single shot
+ timers can be programmed up to one month into the future.
+ Another option here are "repeating timers" which are defined
+ by listing the days of the week on which they shall record.
+ For example, a timer that shall record every monday and wednesday
+ would have a Day setting of "M-W----".
+ Start: The start time of the timer in hh:mm as 24 hour ("military") time.
+ Stop: The stop time of the timer.
+ Priority: The Priority (0..99) is used to decide which timer shall be
+ started in case there are two or more timers with the exact same
+ start time. The first timer in the list with the highest Priority
+ will be used. This value is also stored with the recording and is
+ later used to decide which recording to remove from disk in order
+ to free space for a new recording. If the disk is full and a new
+ recording needs more space, an existing recording with the lowest
+ Priority (and which has exceeded its guaranteed Lifetime) will be
+ removed.
+ Lifetime: The number of days (0..99) a recording made through this timer is
+ guaranteed to remain on disk before it is automatically removed
+ to free up space for a new recording. Note that setting this
+ parameter to very high values for all recordings may soon fill up
+ the entire disk and cause new recordings to fail due to low disk
+ space.
+ File: The name under which a recording created through this timer will
+ be stored on disk (the actual name will also contain the date and
+ time, so it is possible to have a "repeating timer" store all its
+ recordings under the same name; they will be distinguishable by
+ their date and time).
+ If this field is left blank, the channel name will be used to form
+ the name of the recording.
diff --git a/Makefile b/Makefile
index cfa7ef7..8fb678b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,12 @@
#
-# Makefile for the On Screen Menu of the Video Disk Recorder
+# Makefile for the Video Disk Recorder
#
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
-# $Id: Makefile 1.4 2000/06/24 15:09:30 kls Exp $
+# $Id: Makefile 1.5 2000/07/23 11:57:14 kls Exp $
-OBJS = config.o dvbapi.o interface.o menu.o osd.o recording.o remote.o tools.o vdr.o
+OBJS = config.o dvbapi.o interface.o menu.o osd.o recording.o remote.o svdrp.o tools.o vdr.o
ifndef REMOTE
REMOTE = KBD
@@ -28,9 +28,10 @@ dvbapi.o : dvbapi.c config.h dvbapi.h interface.h tools.h
interface.o: interface.c config.h dvbapi.h interface.h remote.h tools.h
menu.o : menu.c config.h dvbapi.h interface.h menu.h osd.h recording.h tools.h
osd.o : osd.c config.h dvbapi.h interface.h osd.h tools.h
-vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h tools.h
+vdr.o : vdr.c config.h dvbapi.h interface.h menu.h osd.h recording.h svdrp.h tools.h
recording.o: recording.c config.h dvbapi.h interface.h recording.h tools.h
remote.o : remote.c remote.h tools.h
+svdrp.o : svdrp.c svdrp.h config.h interface.h tools.h
tools.o : tools.c tools.h
vdr: $(OBJS)
diff --git a/TODO b/TODO
index 64b31b2..09e9a10 100644
--- a/TODO
+++ b/TODO
@@ -8,3 +8,4 @@ TODO list for the Video Disk Recorder project
commercial breaks).
* Implement channel scanning.
* Better support for encrypted channels.
+* Implement remaining commands in SVDRP.
diff --git a/Tools/epg2timers/epg2timers.cxx b/Tools/epg2timers/epg2timers.cxx
new file mode 100644
index 0000000..4a8f333
--- /dev/null
+++ b/Tools/epg2timers/epg2timers.cxx
@@ -0,0 +1,242 @@
+/*
+ * epg2timers.cxx: Convert an EPG "merkliste" page (http://www.tvtv.de) to a timers.conf
+ * file for Klaus Schmidinger's vdr (http://www.cadsoft.de/people/kls/vdr).
+ *
+ * Copyright (C) 2000 Carsten Koch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+ *
+ * The author can be reached at Carsten.Koch@icem.de
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+
+
+static const char date_line[] = "\t<td align=center valign=middle colspan=3><span id=fb-b10>";
+static const char start_time_line[] = " \t\t<td bgcolor=\"#7f98bf\" align=center><span id=\"fb-w14\"><nobr>&nbsp;";
+static const char stop_time_line[] = "\t\t\t<tr><td bgcolor=\"#002b64\" align=center><span id=\"fn-w9\">bis ";
+static const char channel_line[] = "\t\t\t<tr><td bgcolor=\"#002b64\" align=center><span id=\"fb-w9\">";
+static const char title_line[] = "\t\t\t\t<td bgcolor=\"#002b64\" align=left width=100%><span id=\"fb-w10\">";
+static const char summary_line[] = "\t\t\t<table border=0 cellpadding=10 cellspacing=0 bgcolor=\"white\" width=100%>";
+static const char * const channel_names[] =
+{"RTL", "SAT1", "PRO7", "RTL2", "ARD", "BR3", "HR3", "NDR", "SWF", "WDR", "BR Alpha", "SWR BW", "Phoenix",
+ "ZDF", "3sat", "Kinderkanal", "ARTE", "phoenix", "ORF Sat", "ZDF.info", "CNN", "Super RTL", "VOX", "DW TV",
+ "Kabel1", "TM3", "DSF", "HOT", "BloombergTV", "Sky News", "KinderNet", "Alice", "n-tv", "Grand Tour.", "TW1",
+ "Eins Extra", "Eins Festival", "Eins MuXx", "MDR", "ORB", "B1", "ARD Online-Kanal", "Premiere World Promo",
+ "Premiere", "Star Kino", "Cine Action", "Cine Comedy", "Sci Fantasy", "Romantic Movies", "Studio Universal",
+ "TV Niepokalanow", "Mosaico", "Andalucia TV", "TVC Internacional", "Nasza TV", "WishLine test", "Pro 7 Austria",
+ "Kabel 1 Schweiz", "Kabel 1 Austria", "Pro 7 Schweiz", "Kiosque", "KTO", "TCM", "Cartoon Network France & Spain",
+ "TVBS Europe", "TVBS Europe", "Travel", "TCM Espania", "MTV Spain", "TCM France", "RTL2 CH",
+ "La Cinquieme", "ARTE", "Post Filial TV", "Canal Canaris", "Canal Canaris", "Canal Canaris", "Canal Canaris",
+ "AB Sat Passion promo", "AB Channel 1", "Taquilla 0", "CSAT", "Mosaique", "Mosaique 2", "Mosaique 3", "Le Sesame C+",
+ "FEED", "RTM 1", "ESC 1", "TV5 Europe", "TV7 Tunisia", "ARTE", "RAI Uno", "RTP International",
+ "Fashion TV", "VideoService", "Beta Research promo", "Canal Canarias", "TVC International", "Fitur", "Astra Info 1",
+ "Astra Info 2", "Astra Vision 1", "Astra Vision 1", "Astra Vision 1", "Astra Vision 1", "Astra Vision 1",
+ "Astra Vision 1", "Astra Vision 1", "RTL Tele Letzebuerg", "Astra Mosaic", "MHP test", "Bloomberg TV Spain",
+ "Video Italia", "AC 3 promo", ""
+};
+static const int month_lengths[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static const int max_channel = sizeof(channel_names)/sizeof(char *);
+static const int max_title = 50; // maximum length of title file name generated
+static const int max_line = 1024; // line buffer (not used when parsing summary text)
+static const int max_summary = 5000; // Summary can be up to 5000 bytes long
+static const int stop_time_safety_margin = 10; // add 10 minutes to stop time in case start was delayed
+
+
+
+char map_special_char(const char * const word)
+
+{
+ if (strcmp(word, "auml") == 0)
+ return 'ä';
+ else if (strcmp(word, "ouml") == 0)
+ return 'ö';
+ else if (strcmp(word, "uuml") == 0)
+ return 'ü';
+ else if (strcmp(word, "Auml") == 0)
+ return 'Ä';
+ else if (strcmp(word, "Ouml") == 0)
+ return 'Ö';
+ else if (strcmp(word, "Uuml") == 0)
+ return 'Ü';
+ else if (strcmp(word, "szlig") == 0)
+ return 'ß';
+ return ' ';
+}
+
+
+
+
+
+void read_file_name(const char * const line, char * const file_name)
+
+{
+ int line_index = sizeof(title_line) - 1;
+ int title_index = 0;
+ char ch = line[line_index++];
+ do
+ {
+ if (ch == '&')
+ {
+ char word[10];
+ int i = 0;
+ while ((line[line_index + i] != ';') && (i < 9))
+ word[i++] = line[line_index + i];
+ word[i] = 0;
+ ch = map_special_char(word);
+ line_index += i;
+ }
+ switch (ch)
+ {
+ case 'ä': file_name[title_index++] = 'a'; file_name[title_index++] = 'e'; break;
+ case 'ö': file_name[title_index++] = 'o'; file_name[title_index++] = 'e'; break;
+ case 'ü': file_name[title_index++] = 'u'; file_name[title_index++] = 'e'; break;
+ case 'Ä': file_name[title_index++] = 'A'; file_name[title_index++] = 'e'; break;
+ case 'Ö': file_name[title_index++] = 'O'; file_name[title_index++] = 'e'; break;
+ case 'Ü': file_name[title_index++] = 'U'; file_name[title_index++] = 'e'; break;
+ case 'ß': file_name[title_index++] = 's'; file_name[title_index++] = 's'; break;
+ default:
+ if (((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || ((ch >= '0') && (ch <= '9')))
+ file_name[title_index++] = ch;
+ }
+ ch = int(line[line_index++]);
+ } while ((title_index < max_title-1) && (ch != '<') && (ch != 0) && (line_index < max_line-1));
+ file_name[title_index] = 0;
+}
+
+
+
+void read_summary(char * const summary)
+
+{
+ int summary_index = 0;
+ int ch;
+ bool need_space = false;
+ bool done = false;
+ do
+ {
+ ch = getchar();
+ switch (ch)
+ {
+ case '&':
+ {
+ char word[10];
+ int i = 0;
+ ch = getchar();
+ while ((ch != ';') && (ch != EOF) && (i < 9))
+ {
+ word[i++] = ch;
+ ch = getchar();
+ }
+ word[i] = 0;
+ if (need_space) {summary[summary_index++] = ' '; need_space = false;}
+ summary[summary_index++] = map_special_char(word);
+ }
+ break;
+ case '<':
+ {
+ char word[6];
+ int word_index = 0;
+ do
+ {
+ ch = getchar();
+ word[word_index++] = ch;
+ } while ((word_index < 6) && (ch != '>') && (ch != EOF));
+ while ((ch != '>') && (ch != EOF)) ch = getchar();
+ if (strncmp("/table", word, 6) == 0)
+ done = true;
+ }
+ break;
+ default:
+ {
+ if (ch <= ' ')
+ {
+ if (summary_index > 0) need_space = true;
+ }
+ else
+ {
+ if (need_space) {summary[summary_index++] = ' '; need_space = false;}
+ summary[summary_index++] = ch;
+ }
+ }
+ }
+ } while ((summary_index < max_summary - 2) && (!done) && (ch != EOF));
+ summary[summary_index] = 0;
+}
+
+
+
+
+main()
+
+{
+ int channel = 0;
+ int day = -1;
+ int next_day = -1;
+ int start_time = -1;
+ int stop_time = -1;
+ char summary[max_summary] = {0};
+ char file_name[max_title] = {0};
+
+ while (!feof(stdin))
+ {
+ char line[max_line];
+ fgets(line, max_line-1, stdin);
+ if (strncmp(line, date_line, sizeof(date_line)-1) == 0)
+ {
+ const int month = (line[sizeof(date_line) + 6]- '0') * 10 + line[sizeof(date_line) + 7]-'0';
+ day = (line[sizeof(date_line) + 3]- '0') * 10 + line[sizeof(date_line) + 4]-'0';
+ next_day = day == month_lengths[month]? 1 : day + 1;
+ }
+ else if (strncmp(line, start_time_line, sizeof(start_time_line)-1) == 0)
+ {
+ start_time = (line[sizeof(start_time_line) - 1] - '0') * 1000 +
+ (line[sizeof(start_time_line) ] - '0') * 100 +
+ (line[sizeof(start_time_line) + 2] - '0') * 10 +
+ (line[sizeof(start_time_line) + 3] - '0');
+ }
+ else if (strncmp(line, stop_time_line, sizeof(stop_time_line)-1) == 0)
+ {
+ stop_time = ((line[sizeof(stop_time_line) - 1] - '0') * 1000 +
+ (line[sizeof(stop_time_line) ] - '0') * 100 +
+ (line[sizeof(stop_time_line) + 2] - '0') * 10 +
+ (line[sizeof(stop_time_line) + 3] - '0') + stop_time_safety_margin) % 2400;
+ if ((day < 0) || (start_time < 0) || (file_name[0] == 0) || (channel == max_channel))
+ fprintf(stderr, "Input data error.\n");
+ else
+ printf("1:%03d:%02d:%04d:%04d:2:7:%s:%s\n", channel+1, start_time < 600? next_day : day, start_time, stop_time, file_name, summary);
+ start_time = -1; stop_time = -1; file_name[0] = 0; summary[0] = 0; channel = max_channel;
+ }
+ else if (strncmp(line, title_line, sizeof(title_line)-1) == 0)
+ read_file_name(line, file_name);
+ else if (strncmp(line, channel_line, sizeof(channel_line)-1) == 0)
+ {
+ int i = sizeof(channel_line);
+ while ((i < max_line-1) && (line[i] != '<')) i++;
+ line[i] = 0; // end of string
+ for (channel = 0; (channel < max_channel) &&
+ (strcmp(line + sizeof(channel_line) - 1, channel_names[channel]) != 0);
+ channel++);
+ if (channel == max_channel)
+ fprintf(stderr, "Error - channel '%s' not recognized.\n", line + sizeof(channel_line) - 1);
+ }
+ else if (strncmp(line, summary_line, sizeof(summary_line)-1) == 0)
+ read_summary(summary);
+ }
+}
diff --git a/Tools/xtvrc2vdr/Makefile b/Tools/xtvrc2vdr/Makefile
new file mode 100644
index 0000000..be50541
--- /dev/null
+++ b/Tools/xtvrc2vdr/Makefile
@@ -0,0 +1,16 @@
+#
+# Makefile for xtvrc2vdr utility
+#
+
+OBJS = xtvrc2vdr.o
+
+%.o: %.c
+ gcc -g -O2 -Wall -c $(DEFINES) $<
+
+all: xtvrc2vdr
+
+xtvrc2vdr: $(OBJS)
+ gcc -g -O2 $(OBJS) -o xtvrc2vdr
+
+clean:
+ -rm -f $(OBJS) xtvrc2vdr
diff --git a/Tools/xtvrc2vdr/hotbird.conf b/Tools/xtvrc2vdr/hotbird.conf
new file mode 100644
index 0000000..3431d5c
--- /dev/null
+++ b/Tools/xtvrc2vdr/hotbird.conf
@@ -0,0 +1,191 @@
+TV Polonia:10719:v:1:27500:163:92:0:0
+Credit Agricole:10834:v:1:27500:5321:5333:0:0
+La Chaine Parlementaire:10873:v:1:27500:1020:1030:0:0
+TMT:10892:v:1:27500:163:92:0:0
+Multivision Accueil:10911:v:1:27500:320:330:0:0
+RTL:11054:v:1:27500:160:80:0:0
+VOX:11054:v:1:27500:500:501:0:0
+Sat 1 A:11054:v:1:27500:511:512:0:0
+RTL II Austria:11054:v:1:27500:520:521:0:0
+NBC Europe:11054:v:1:27500:550:551:0:0
+ZDF:11054:v:1:27500:570:571:0:0
+K-T9:11054:v:1:27500:580:581:0:0
+Sat 1 Schweiz:11604:v:1:27500:101:102:0:0
+MKT9:11623:v:1:27500:222:242:0:0
+Olisat TV Promo:11623:v:1:27500:226:246:0:0
+Bloomberg TV Germany:11642:v:1:27500:1460:1420:0:0
+Bloomberg TV UK:11642:v:1:27500:1560:1520:0:0
+SAT 7:11642:v:1:27500:1660:1620:0:0
+Multivision 1:11662:v:1:27500:120:130:0:0
+Dubai EDT9:11746:v:1:27500:4130:4131:0:0
+Dubai Sport Channel:11746:v:1:27500:4386:4387:0:0
+Dubai Business Channel:11746:v:1:27500:4642:4643:0:0
+Dubai EDT9:11746:v:1:27500:4898:4899:0:0
+RAI Uno:11766:v:1:27500:160:80:0:0
+RAI Due:11766:v:1:27500:161:84:0:0
+RAI Tre:11766:v:1:27500:162:88:0:0
+RAI Mosaico:11766:v:1:27500:518:8191:0:0
+RAI SportSat:11804:v:1:27500:512:650:0:0
+RAI Nettuno Sat 2:11804:v:1:27500:513:651:0:0
+RAI Educational:11804:v:1:27500:514:652:0:0
+TelePace :11804:v:1:27500:515:653:0:0
+RAI News24:11804:v:1:27500:516:654:0:0
+Camera dei Deputati:11804:v:1:27500:517:655:0:0
+SAT 2000:11804:v:1:27500:518:656:0:0
+RAI NettunoSat 1:11804:v:1:27500:519:657:0:0
+ERT Sat:11823:v:1:27500:521:740:0:0
+INT9:11843:v:1:27500:2324:2325:0:0
+TVL:11843:v:1:27500:2441:2442:0:0
+Team TV :11881:v:1:27500:2305:2306:0:0
+Ante Prima:11881:v:1:27500:2435:2436:0:0
+SNAI:11881:v:1:27500:2561:2562:0:0
+Italia 1 :11919:v:1:27500:512:650:0:0
+Canale 5:11919:v:1:27500:513:660:0:0
+Rete 4 :11919:v:1:27500:514:670:0:0
+ART Europe:12015:v:1:27500:164:96:0:0
+ESC 2:12015:v:1:27500:166:104:0:0
+ART Iqra:12015:v:1:27500:168:112:0:0
+Vetrina D+:12034:v:1:27500:166:105:0:0
+D+ Info:12073:v:1:27500:160:80:0:0
+Palco Promo:12073:v:1:27500:161:84:0:0
+Vacaciones T9:12092:v:1:27500:4112:4113:0:0
+TvL - TV Locale:12092:v:1:27500:4160:4161:0:0
+Satisfaction T9:12092:v:1:27500:4192:4193:0:0
+TVE Internacional:12092:v:1:27500:4208:4209:0:0
+TVG - TV de Galicia :12092:v:1:27500:4224:4225:0:0
+La Cadena Del Milagro:12092:v:1:27500:4368:4369:0:0
+Fiesta:12092:v:1:27500:4432:4433:0:0
+Visions Europe:12092:v:1:27500:4416:4417:0:0
+SateliTV/TV Sex Channel:12092:v:1:27500:4480:4481:0:0
+Krisma:12111:v:1:27500:200:201:0:0
+NT9:12111:v:1:27500:210:211:0:0
+Armenia TV 1:12111:v:1:27500:240:241:0:0
+SMAU Channel :12111:v:1:27500:260:261:0:0
+JSC - Al Jazeera Satellite Ch :12111:v:1:27500:270:271:0:0
+Il Tirreno Sat:12111:v:1:27500:280:301:0:0
+Coming Soon T9:12111:v:1:27500:310:311:0:0
+Alice:12149:v:1:27500:160:161:0:0
+Nuvolari Promo:12149:v:1:27500:176:177:0:0
+CCTV 4:12169:v:1:27500:516:690:0:0
+Kanali Vuolis:12169:v:1:27500:517:700:0:0
+Nova Promo:12169:v:1:27500:521:740:0:0
+ERT Sat :12188:v:1:27500:514:652:0:0
+Kanali Voulis:12188:v:1:27500:515:653:0:0
+OTE Promo:12188:v:1:27500:517:655:0:0
+TV 5 Europe:12245:v:1:27500:121:131:0:0
+Fashion T9:12245:v:1:27500:123:133:0:0
+TV Ajara:12245:v:1:27500:127:137:0:0
+Telekom T9:12265:v:1:27500:1460:1420:0:0
+SLO-TV1:12303:v:1:27500:200:201:0:0
+Polonia 1:12303:v:1:27500:205:206:0:0
+Super 1:12303:v:1:27500:207:208:0:0
+Sicilia Internacional:12303:v:1:27500:210:211:0:0
+SicilSat:12303:v:1:27500:225:226:0:0
+TBNE Italy:12303:v:1:27500:230:231:0:0
+Countdown T9:12303:v:1:27500:235:236:0:0
+Napoli International:12303:v:1:27500:240:241:0:0
+Magic T9:12303:v:1:27500:245:246:0:0
+TEST:12341:v:1:27500:165:108:0:0
+Colour Bars:12380:v:1:27500:3022:3032:0:0
+Tele 24 :12380:v:1:27500:3023:3033:0:0
+Abu Dhabi TV :12380:v:1:27500:3024:3034:0:0
+LCA:12380:v:1:27500:3025:3035:0:0
+RTV Montenegro:12380:v:1:27500:3026:3036:0:0
+SRG SSR Sat Access :12399:v:1:27500:165:98:0:0
+Jam-e-Jam Network 1 (IRIB 1):12437:v:1:27500:160:80:0:0
+Jam-e-Jam Network 2 (IRIB 2):12437:v:1:27500:161:82:0:0
+Sahar University Network:12437:v:1:27500:162:84:0:0
+Maharishi Open University:12476:v:1:27500:42:43:0:0
+Europe by Satellite:12476:v:1:27500:101:201:0:0
+Pink Backup:12476:v:1:27500:308:256:0:0
+Mizik Tropical:12476:v:1:27500:435:436:0:0
+TLI info card:12476:v:1:27500:771:768:0:0
+Liberty T9:12476:v:1:27500:941:942:0:0
+HRT TV 1:12520:v:1:27500:100:101:0:0
+HRT National:12520:v:1:27500:107:108:0:0
+BVN TV:12520:v:1:27500:210:211:0:0
+Sicilia International:12520:v:1:27500:501:502:0:0
+Sardegna Uno:12520:v:1:27500:503:504:0:0
+TGRT:12520:v:1:27500:505:506:0:0
+Euro Mediterraneo:12520:v:1:27500:510:511:0:0
+WWWTravel T9:12540:v:1:27500:1180:1183:0:0
+WWWTravel T9:12540:v:1:27500:1180:1184:0:0
+WWWTravel T9:12540:v:1:27500:1180:1185:0:0
+Bulgaria T9:12540:v:1:27500:4612:4613:0:0
+MC Sat Monte Carlo:12540:v:1:27500:5126:5122:0:0
+MBC:12597:v:1:27500:160:80:0:0
+SIMA-YEH-MOGHAVEMENT:12597:v:1:27500:161:84:0:0
+NITV (National Iran TV ):12597:v:1:27500:163:92:0:0
+BET International:12597:v:1:27500:167:108:0:0
+JSTV 2 Info Card:12597:v:1:27500:2011:2012:0:0
+EuroNews:12597:v:1:27500:2221:2231:0:0
+EuroNews:12597:v:1:27500:2221:2232:0:0
+EuroNews:12597:v:1:27500:2221:2233:0:0
+EuroNews:12597:v:1:27500:2221:2234:0:0
+EuroNews:12597:v:1:27500:2221:2235:0:0
+EuroNews:12597:v:1:27500:2221:2236:0:0
+EuroNews:12597:v:1:27500:2221:2237:0:0
+Canal Agro Rual:12597:v:1:27500:2321:2331:0:0
+MMO9:12616:v:1:27500:2561:2562:0:0
+Dubai Sport Channel:12654:v:1:27500:1060:1020:0:0
+Sharjah TV :12654:v:1:27500:1160:1120:0:0
+Qatar T9:12654:v:1:27500:1260:1220:0:0
+Saudi Channel 1 :12654:v:1:27500:1360:1320:0:0
+Kuwait Space Channel :12654:v:1:27500:1460:1420:0:0
+Libya T9:12654:v:1:27500:1560:1520:0:0
+Sudan T9:12654:v:1:27500:1660:1620:0:0
+Oman T9:12654:v:1:27500:1760:1720:0:0
+Jordan Satellite Channel:12654:v:1:27500:1860:1820:0:0
+Iraq Satellite Channel:12654:v:1:27500:1960:1920:0:0
+Thai TV 5 Global Network :12673:v:1:27500:200:201:0:0
+DigItaly:12673:v:1:27500:220:221:0:0
+Studio Europa:12673:v:1:27500:230:231:0:0
+Game Network:12673:v:1:27500:291:292:0:0
+Video Italia :12673:v:1:27500:340:341:0:0
+Telemarket:12673:v:1:27500:350:351:0:0
+Evision:12673:v:1:27500:360:361:0:0
+AB Passion:12692:v:1:27500:160:80:0:0
+Onyx T9:12692:v:1:27500:161:84:0:0
+EWTN:10723:v:1:29900:1001:1201:0:0
+Test (Newslynx):10723:v:1:29900:1002:1202:0:0
+MTA International:10723:v:1:29900:1004:1204:0:0
+J TV Test:10992:v:1:27500:2436:2437:0:0
+Bloomberg UK Test Card:11242:v:1:27500:162:88:0:0
+Channel SUN Test (KBT):11604:v:1:27500:111:112:0:0
+Racing Channel Test:11623:v:1:27500:223:243:0:0
+Test Card (pgm 4):11623:v:1:27500:224:244:0:0
+Olisat TLC test card:11623:v:1:27500:225:245:0:0
+Channel SUN Test (KBT):11623:v:1:27500:229:249:0:0
+Rai way 3 test card:11766:v:1:27500:164:96:0:0
+Rai way 1 test card:11766:v:1:27500:515:653:0:0
+Rai way 2 test card:11766:v:1:27500:516:654:0:0
+Test (Local Satellite):12092:v:1:27500:4176:4177:0:0
+Retelsat Test:12092:v:1:27500:4464:4465:0:0
+AIT Test Card:12111:v:1:27500:220:221:0:0
+Fucino Test Card:12111:v:1:27500:230:231:0:0
+Espresso(Antenna Hungaria Test Card):12149:v:1:27500:36:37:0:0
+Antenna Hungaria Test Card:12149:v:1:27500:96:97:0:0
+Antenna Hungaria Test Card:12149:v:1:27500:112:113:0:0
+Leonardo (Antenna Hungaria Test):12149:v:1:27500:128:129:0:0
+Test (Sahar):12437:v:1:27500:163:86:0:0
+Test 1:12437:v:1:27500:164:88:0:0
+Test 2:12437:v:1:27500:165:90:0:0
+CNES-Toulouse test:12558:v:1:27500:6143:6142:0:0
+Test Card:12597:v:1:27500:161:84:0:0
+FEED:11242:v:1:27500:167:108:0:0
+Feed :11623:v:1:27500:221:241:0:0
+Quantum 24 :10913:v:1:3998:1160:1120:0:0
+Quantum 24:10913:v:1:3998:1160:1220:0:0
+VIVA Polska:11131:v:1:4340:98:99:0:0
+Deutsche Welle T9:11196:v:1:9096:101:102:0:0
+Canal 24 Horas :11205:v:1:4000:4130:4131:0:0
+TV 5 Asie :11338:v:1:5632:512:640:0:0
+RAI4IFA:11548:v:1:4398:512:650:0:0
+Pro TV International:12201:v:1:5632:1160:1120:0:0
+TVN Polnoc:12211:v:1:5632:4194:4195:0:0
+WorldNet Europe:12484:v:1:8298:4260:4220:0:0
+WorldNet Europe:12484:v:1:8298:4360:4320:0:0
+WorldNet Europe:12484:v:1:8298:4460:4420:0:0
+WorldNet Europe:12484:v:1:8298:4560:4520:0:0
+TVN Polnoc:12573:v:1:5632:4194:4195:0:0
+APTN:12582:v:1:5632:308:256:0:0
diff --git a/Tools/xtvrc2vdr/xtvrc.hotbird b/Tools/xtvrc2vdr/xtvrc.hotbird
new file mode 100644
index 0000000..c809eee
--- /dev/null
+++ b/Tools/xtvrc2vdr/xtvrc.hotbird
@@ -0,0 +1,1337 @@
+*
+Channel: TV Polonia
+Frequency: 10719
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 163 92 0 10
+
+*
+Channel: Credit Agricole
+Frequency: 10834
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 5321 5333 0 10
+
+*
+Channel: La Chaine Parlementaire
+Frequency: 10873
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 1020 1030 0 10
+
+*
+Channel: TMT
+Frequency: 10892
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 163 92 0 10
+
+*
+Channel: Multivision Accueil
+Frequency: 10911
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 320 330 0 10
+
+*
+Channel: RTL
+Frequency: 11054
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 160 80 0 10
+
+*
+Channel: VOX
+Frequency: 11054
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 500 501 0 10
+
+*
+Channel: Sat 1 A
+Frequency: 11054
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 511 512 0 10
+
+*
+Channel: RTL II Austria
+Frequency: 11054
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 520 521 0 10
+
+*
+Channel: NBC Europe
+Frequency: 11054
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 550 551 0 10
+
+*
+Channel: ZDF
+Frequency: 11054
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 570 571 0 10
+
+*
+Channel: K-T9
+Frequency: 11054
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 580 581 0 10
+
+*
+Channel: Sat 1 Schweiz
+Frequency: 11604
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 101 102 0 10
+
+*
+Channel: MKT9
+Frequency: 11623
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 222 242 0 10
+
+*
+Channel: Olisat TV Promo
+Frequency: 11623
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 226 246 0 10
+
+*
+Channel: Bloomberg TV Germany
+Frequency: 11642
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1460 1420 0 10
+
+*
+Channel: Bloomberg TV UK
+Frequency: 11642
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1560 1520 0 10
+
+*
+Channel: SAT 7
+Frequency: 11642
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1660 1620 0 10
+
+*
+Channel: Multivision 1
+Frequency: 11662
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 120 130 0 10
+
+*
+Channel: Dubai EDT9
+Frequency: 11746
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4130 4131 0 10
+
+*
+Channel: Dubai Sport Channel
+Frequency: 11746
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4386 4387 0 10
+
+*
+Channel: Dubai Business Channel
+Frequency: 11746
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4642 4643 0 10
+
+*
+Channel: Dubai EDT9
+Frequency: 11746
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4898 4899 0 10
+
+*
+Channel: RAI Uno
+Frequency: 11766
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 160 80 0 10
+
+*
+Channel: RAI Due
+Frequency: 11766
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 161 84 0 10
+
+*
+Channel: RAI Tre
+Frequency: 11766
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 162 88 0 10
+
+*
+Channel: RAI Mosaico
+Frequency: 11766
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 518 8191 0 10
+
+*
+Channel: RAI SportSat
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 512 650 0 10
+
+*
+Channel: RAI Nettuno Sat 2
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 513 651 0 10
+
+*
+Channel: RAI Educational
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 514 652 0 10
+
+*
+Channel: TelePace
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 515 653 0 10
+
+*
+Channel: RAI News24
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 516 654 0 10
+
+*
+Channel: Camera dei Deputati
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 517 655 0 10
+
+*
+Channel: SAT 2000
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 518 656 0 10
+
+*
+Channel: RAI NettunoSat 1
+Frequency: 11804
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 519 657 0 10
+
+*
+Channel: ERT Sat
+Frequency: 11823
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 521 740 0 10
+
+*
+Channel: INT9
+Frequency: 11843
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2324 2325 0 10
+
+*
+Channel: TVL
+Frequency: 11843
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2441 2442 0 10
+
+*
+Channel: Team TV
+Frequency: 11881
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2305 2306 0 10
+
+*
+Channel: Ante Prima
+Frequency: 11881
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2435 2436 0 10
+
+*
+Channel: SNAI
+Frequency: 11881
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2561 2562 0 10
+
+*
+Channel: Italia 1
+Frequency: 11919
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 512 650 0 10
+
+*
+Channel: Canale 5
+Frequency: 11919
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 513 660 0 10
+
+*
+Channel: Rete 4
+Frequency: 11919
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 514 670 0 10
+
+*
+Channel: ART Europe
+Frequency: 12015
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 164 96 0 10
+
+*
+Channel: ESC 2
+Frequency: 12015
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 166 104 0 10
+
+*
+Channel: ART Iqra
+Frequency: 12015
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 168 112 0 10
+
+*
+Channel: Vetrina D+
+Frequency: 12034
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 166 105 0 10
+
+*
+Channel: D+ Info
+Frequency: 12073
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 160 80 0 10
+
+*
+Channel: Palco Promo
+Frequency: 12073
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 161 84 0 10
+
+*
+Channel: Vacaciones T9
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4112 4113 0 10
+
+*
+Channel: TvL - TV Locale
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4160 4161 0 10
+
+*
+Channel: Satisfaction T9
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4192 4193 0 10
+
+*
+Channel: TVE Internacional
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4208 4209 0 10
+
+*
+Channel: TVG - TV de Galicia
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4224 4225 0 10
+
+*
+Channel: La Cadena Del Milagro
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4368 4369 0 10
+
+*
+Channel: Fiesta
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4432 4433 0 10
+
+*
+Channel: Visions Europe
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4416 4417 0 10
+
+*
+Channel: SateliTV/TV Sex Channel
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4480 4481 0 10
+
+*
+Channel: Krisma
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 200 201 0 10
+
+*
+Channel: NT9
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 210 211 0 10
+
+*
+Channel: Armenia TV 1
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 240 241 0 10
+
+*
+Channel: SMAU Channel
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 260 261 0 10
+
+*
+Channel: JSC - Al Jazeera Satellite Ch
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 270 271 0 10
+
+*
+Channel: Il Tirreno Sat
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 280 301 0 10
+
+*
+Channel: Coming Soon T9
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 310 311 0 10
+
+*
+Channel: Alice
+Frequency: 12149
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 160 161 0 10
+
+*
+Channel: Nuvolari Promo
+Frequency: 12149
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 176 177 0 10
+
+*
+Channel: CCTV 4
+Frequency: 12169
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 516 690 0 10
+
+*
+Channel: Kanali Vuolis
+Frequency: 12169
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 517 700 0 10
+
+*
+Channel: Nova Promo
+Frequency: 12169
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 521 740 0 10
+
+*
+Channel: ERT Sat
+Frequency: 12188
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 514 652 0 10
+
+*
+Channel: Kanali Voulis
+Frequency: 12188
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 515 653 0 10
+
+*
+Channel: OTE Promo
+Frequency: 12188
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 517 655 0 10
+
+*
+Channel: TV 5 Europe
+Frequency: 12245
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 121 131 0 10
+
+*
+Channel: Fashion T9
+Frequency: 12245
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 123 133 0 10
+
+*
+Channel: TV Ajara
+Frequency: 12245
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 127 137 0 10
+
+*
+Channel: Telekom T9
+Frequency: 12265
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 1460 1420 0 10
+
+*
+Channel: SLO-TV1
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 200 201 0 10
+
+*
+Channel: Polonia 1
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 205 206 0 10
+
+*
+Channel: Super 1
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 207 208 0 10
+
+*
+Channel: Sicilia Internacional
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 210 211 0 10
+
+*
+Channel: SicilSat
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 225 226 0 10
+
+*
+Channel: TBNE Italy
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 230 231 0 10
+
+*
+Channel: Countdown T9
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 235 236 0 10
+
+*
+Channel: Napoli International
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 240 241 0 10
+
+*
+Channel: Magic T9
+Frequency: 12303
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 245 246 0 10
+
+*
+Channel: TEST
+Frequency: 12341
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 165 108 0 10
+
+*
+Channel: Colour Bars
+Frequency: 12380
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 3022 3032 0 10
+
+*
+Channel: Tele 24
+Frequency: 12380
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 3023 3033 0 10
+
+*
+Channel: Abu Dhabi TV
+Frequency: 12380
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 3024 3034 0 10
+
+*
+Channel: LCA
+Frequency: 12380
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 3025 3035 0 10
+
+*
+Channel: RTV Montenegro
+Frequency: 12380
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 3026 3036 0 10
+
+*
+Channel: SRG SSR Sat Access
+Frequency: 12399
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 165 98 0 10
+
+*
+Channel: Jam-e-Jam Network 1 (IRIB 1)
+Frequency: 12437
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 160 80 0 10
+
+*
+Channel: Jam-e-Jam Network 2 (IRIB 2)
+Frequency: 12437
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 161 82 0 10
+
+*
+Channel: Sahar University Network
+Frequency: 12437
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 162 84 0 10
+
+*
+Channel: Maharishi Open University
+Frequency: 12476
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 42 43 0 10
+
+*
+Channel: Europe by Satellite
+Frequency: 12476
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 101 201 0 10
+
+*
+Channel: Pink Backup
+Frequency: 12476
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 308 256 0 10
+
+*
+Channel: Mizik Tropical
+Frequency: 12476
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 435 436 0 10
+
+*
+Channel: TLI info card
+Frequency: 12476
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 771 768 0 10
+
+*
+Channel: Liberty T9
+Frequency: 12476
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 941 942 0 10
+
+*
+Channel: HRT TV 1
+Frequency: 12520
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 100 101 0 10
+
+*
+Channel: HRT National
+Frequency: 12520
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 107 108 0 10
+
+*
+Channel: BVN TV
+Frequency: 12520
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 210 211 0 10
+
+*
+Channel: Sicilia International
+Frequency: 12520
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 501 502 0 10
+
+*
+Channel: Sardegna Uno
+Frequency: 12520
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 503 504 0 10
+
+*
+Channel: TGRT
+Frequency: 12520
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 505 506 0 10
+
+*
+Channel: Euro Mediterraneo
+Frequency: 12520
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 510 511 0 10
+
+*
+Channel: WWWTravel T9
+Frequency: 12540
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1180 1183 0 10
+
+*
+Channel: WWWTravel T9
+Frequency: 12540
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1180 1184 0 10
+
+*
+Channel: WWWTravel T9
+Frequency: 12540
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1180 1185 0 10
+
+*
+Channel: Bulgaria T9
+Frequency: 12540
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4612 4613 0 10
+
+*
+Channel: MC Sat Monte Carlo
+Frequency: 12540
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 5126 5122 0 10
+
+*
+Channel: MBC
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 160 80 0 10
+
+*
+Channel: SIMA-YEH-MOGHAVEMENT
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 161 84 0 10
+
+*
+Channel: NITV (National Iran TV )
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 163 92 0 10
+
+*
+Channel: BET International
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 167 108 0 10
+
+*
+Channel: JSTV 2 Info Card
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2011 2012 0 10
+
+*
+Channel: EuroNews
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2221 2231 0 10
+
+*
+Channel: EuroNews
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2221 2232 0 10
+
+*
+Channel: EuroNews
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2221 2233 0 10
+
+*
+Channel: EuroNews
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2221 2234 0 10
+
+*
+Channel: EuroNews
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2221 2235 0 10
+
+*
+Channel: EuroNews
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2221 2236 0 10
+
+*
+Channel: EuroNews
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2221 2237 0 10
+
+*
+Channel: Canal Agro Rual
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2321 2331 0 10
+
+*
+Channel: MMO9
+Frequency: 12616
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 2561 2562 0 10
+
+*
+Channel: Dubai Sport Channel
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1060 1020 0 10
+
+*
+Channel: Sharjah TV
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1160 1120 0 10
+
+*
+Channel: Qatar T9
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1260 1220 0 10
+
+*
+Channel: Saudi Channel 1
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1360 1320 0 10
+
+*
+Channel: Kuwait Space Channel
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1460 1420 0 10
+
+*
+Channel: Libya T9
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1560 1520 0 10
+
+*
+Channel: Sudan T9
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1660 1620 0 10
+
+*
+Channel: Oman T9
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1760 1720 0 10
+
+*
+Channel: Jordan Satellite Channel
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1860 1820 0 10
+
+*
+Channel: Iraq Satellite Channel
+Frequency: 12654
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 1960 1920 0 10
+
+*
+Channel: Thai TV 5 Global Network
+Frequency: 12673
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 200 201 0 10
+
+*
+Channel: DigItaly
+Frequency: 12673
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 220 221 0 10
+
+*
+Channel: Studio Europa
+Frequency: 12673
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 230 231 0 10
+
+*
+Channel: Game Network
+Frequency: 12673
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 291 292 0 10
+
+*
+Channel: Video Italia
+Frequency: 12673
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 340 341 0 10
+
+*
+Channel: Telemarket
+Frequency: 12673
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 350 351 0 10
+
+*
+Channel: Evision
+Frequency: 12673
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 360 361 0 10
+
+*
+Channel: AB Passion
+Frequency: 12692
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 160 80 0 10
+
+*
+Channel: Onyx T9
+Frequency: 12692
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 161 84 0 10
+
+*
+Channel: EWTN
+Frequency: 10723
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 29900 2 1001 1201 0 10
+
+*
+Channel: Test (Newslynx)
+Frequency: 10723
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 29900 2 1002 1202 0 10
+
+*
+Channel: MTA International
+Frequency: 10723
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 29900 2 1004 1204 0 10
+
+*
+Channel: J TV Test
+Frequency: 10992
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 2436 2437 0 10
+
+*
+Channel: Bloomberg UK Test Card
+Frequency: 11242
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 162 88 0 10
+
+*
+Channel: Channel SUN Test (KBT)
+Frequency: 11604
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 4 111 112 0 10
+
+*
+Channel: Racing Channel Test
+Frequency: 11623
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 223 243 0 10
+
+*
+Channel: Test Card (pgm 4)
+Frequency: 11623
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 224 244 0 10
+
+*
+Channel: Olisat TLC test card
+Frequency: 11623
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 225 245 0 10
+
+*
+Channel: Channel SUN Test (KBT)
+Frequency: 11623
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 229 249 0 10
+
+*
+Channel: Rai way 3 test card
+Frequency: 11766
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 164 96 0 10
+
+*
+Channel: Rai way 1 test card
+Frequency: 11766
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 515 653 0 10
+
+*
+Channel: Rai way 2 test card
+Frequency: 11766
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 1 516 654 0 10
+
+*
+Channel: Test (Local Satellite)
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4176 4177 0 10
+
+*
+Channel: Retelsat Test
+Frequency: 12092
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 4464 4465 0 10
+
+*
+Channel: AIT Test Card
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 220 221 0 10
+
+*
+Channel: Fucino Test Card
+Frequency: 12111
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 230 231 0 10
+
+*
+Channel: Espresso(Antenna Hungaria Test Card)
+Frequency: 12149
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 36 37 0 10
+
+*
+Channel: Antenna Hungaria Test Card
+Frequency: 12149
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 96 97 0 10
+
+*
+Channel: Antenna Hungaria Test Card
+Frequency: 12149
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 112 113 0 10
+
+*
+Channel: Leonardo (Antenna Hungaria Test)
+Frequency: 12149
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 128 129 0 10
+
+*
+Channel: Test (Sahar)
+Frequency: 12437
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 163 86 0 10
+
+*
+Channel: Test 1
+Frequency: 12437
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 164 88 0 10
+
+*
+Channel: Test 2
+Frequency: 12437
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 27500 2 165 90 0 10
+
+*
+Channel: CNES-Toulouse test
+Frequency: 12558
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 6143 6142 0 10
+
+*
+Channel: Test Card
+Frequency: 12597
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 161 84 0 10
+
+*
+Channel: FEED
+Frequency: 11242
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 167 108 0 10
+
+*
+Channel: Feed
+Frequency: 11623
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 27500 2 221 241 0 10
+
+*
+Channel: Quantum 24
+Frequency: 10913
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 3998 0 1160 1120 0 10
+
+*
+Channel: Quantum 24
+Frequency: 10913
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 3998 0 1160 1220 0 10
+
+*
+Channel: VIVA Polska
+Frequency: 11131
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 4340 2 98 99 0 10
+
+*
+Channel: Deutsche Welle T9
+Frequency: 11196
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 9096 0 101 102 0 10
+
+*
+Channel: Canal 24 Horas
+Frequency: 11205
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 4000 2 4130 4131 0 10
+
+*
+Channel: TV 5 Asie
+Frequency: 11338
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 5632 2 512 640 0 10
+
+*
+Channel: RAI4IFA
+Frequency: 11548
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 4398 6 512 650 0 10
+
+*
+Channel: Pro TV International
+Frequency: 12201
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 5632 6 1160 1120 0 10
+
+*
+Channel: TVN Polnoc
+Frequency: 12211
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 5632 2 4194 4195 0 10
+
+*
+Channel: WorldNet Europe
+Frequency: 12484
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 8298 2 4260 4220 0 10
+
+*
+Channel: WorldNet Europe
+Frequency: 12484
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 8298 2 4360 4320 0 10
+
+*
+Channel: WorldNet Europe
+Frequency: 12484
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 8298 2 4460 4420 0 10
+
+*
+Channel: WorldNet Europe
+Frequency: 12484
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 9 8298 2 4560 4520 0 10
+
+*
+Channel: TVN Polnoc
+Frequency: 12573
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 5632 2 4194 4195 0 10
+
+*
+Channel: APTN
+Frequency: 12582
+CBHC: 255 0 0 255
+NI: -1 0
+SAT: 10 5632 2 308 256 0 10
+
diff --git a/Tools/xtvrc2vdr/xtvrc2vdr.c b/Tools/xtvrc2vdr/xtvrc2vdr.c
new file mode 100644
index 0000000..772db66
--- /dev/null
+++ b/Tools/xtvrc2vdr/xtvrc2vdr.c
@@ -0,0 +1,146 @@
+/*
+ * * xtvrc2vdr.c: Converts 'xtvrc' files to 'vdr' channel format
+ * *
+ * * Copyright (C) 2000 Plamen Ganev
+ * *
+ * * This program is free software; you can redistribute it and/or
+ * * modify it under the terms of the GNU General Public License
+ * * as published by the Free Software Foundation; either version 2
+ * * of the License, or (at your option) any later version.
+ * *
+ * * This program is distributed in the hope that it will be useful,
+ * * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * * GNU General Public License for more details.
+ * *
+ * * You should have received a copy of the GNU General Public License
+ * * along with this program; if not, write to the Free Software
+ * * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
+ * *
+ * * The author can be reached at pganev@comm.it
+ * *
+ * */
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#define MAX_LINE_LEN 1024
+#define MAX_NAME 100
+#define TOKS ": \n\r"
+#define NAMETOKS ":\n\r"
+
+typedef struct {
+ char Name[MAX_NAME+1];
+ int freq;
+ int color, hue, bright, saturation ;
+ int nitv, input ;
+ int pol, srate, fec, vpid, apid, lnbnum, type;
+} CHANNEL_DATA ;
+
+void strlwr( char *s ){
+ while ( s && *s ){
+ *s = tolower(*s);
+ s++;
+ }
+}
+
+int ReadChannel( FILE *f, CHANNEL_DATA *channel ) {
+ static char s[MAX_LINE_LEN+1];
+ char *p;
+
+ memset( channel, sizeof( CHANNEL_DATA ), 0 ) ;
+
+ while ((p=fgets( s, MAX_LINE_LEN, f ))!=NULL){
+// printf("%s", s ) ;
+ if (s[0] == '*')
+ break ;
+ }
+
+ if ( !p ) { /* EOF? */
+// printf("EOF\n");
+ return 0 ;
+ }
+
+ while (fgets( s, MAX_LINE_LEN, f )){
+ if ( s[0] == '\n' )
+ return channel->freq ? 1 : 0;
+ p = strtok( s, TOKS ) ;
+ if ( !p ) {
+ return 0;
+ }
+ strlwr( p ) ;
+ if ( !strcmp( p, "channel" )){
+ p=strtok( NULL, NAMETOKS );
+ while ( p && *p==' ')
+ p++;
+ strcpy( channel->Name, p );
+// printf("%d ", channel->freq ) ;
+ } else if ( !strcmp( p, "frequency")) {
+ channel->freq = atoi( p=strtok( NULL, TOKS ));
+// printf("%d ", channel->freq ) ;
+ } else if ( !strcmp( p, "cbhc")) {
+ channel->color = atoi(p=strtok(NULL,TOKS));
+ channel->hue = atoi(p=strtok(NULL,TOKS));
+ channel->bright = atoi(p=strtok(NULL,TOKS));
+ channel->saturation = atoi(p=strtok(NULL,TOKS));
+ } else if ( !strcmp( p, "ni")) {
+ channel->nitv = atoi(p=strtok(NULL,TOKS));
+ channel->input = atoi(p=strtok(NULL,TOKS));
+ } else if ( !strcmp( p, "sat")) {
+ channel->pol = atoi(p=strtok(NULL,TOKS));
+ channel->srate = atoi(p=strtok(NULL,TOKS));
+ channel->fec = atoi(p=strtok(NULL,TOKS));
+ channel->vpid = atoi(p=strtok(NULL,TOKS));
+ channel->apid = atoi(p=strtok(NULL,TOKS));
+ channel->lnbnum = atoi(p=strtok(NULL,TOKS));
+ channel->type = atoi(p=strtok(NULL,TOKS));
+ } else
+ printf("Unknown token %s\n", p ) ;
+ }
+ return 1 ;
+}
+
+int main ( int argc, char *argv[] ){
+ FILE *f, *fo ;
+ int cnt = 0;
+ CHANNEL_DATA channel ;
+
+ if ( argc != 3 ){
+ printf("USAGE: %s <xtvrc file> <vdr file>\n\n", argv[0] ) ;
+ return 0;
+ }
+
+ if ( !(f=fopen(argv[1], "rt"))){
+ printf("Can't open %s for reading\n\n", argv[1]);
+ return 0;
+ }
+
+ if ( !(fo=fopen(argv[2], "wt"))){
+ printf("Can't open %s for writing\n\n", argv[2]);
+ return 0;
+ }
+
+ while ( ReadChannel( f, &channel ) ) {
+ cnt++;
+ fprintf(fo, "%s:%d:%c:%d:%d:%d:%d:%d:%d\n",
+ channel.Name ,
+ channel.freq ,
+ channel.pol ? 'v' : 'h' ,
+ 1, //channel.lnbnum ,
+ channel.srate ,
+ channel.vpid ,
+ channel.apid ,
+ 0, //channel.type ,
+ 0 ); //channel.fec ) ;
+ }
+
+ printf( "%d channels read.\n\n", cnt ) ;
+
+ fclose(f);
+ fclose(fo);
+ return 1;
+}
diff --git a/channels.conf b/channels.conf
index d59b83b..870a6d6 100644
--- a/channels.conf
+++ b/channels.conf
@@ -31,12 +31,12 @@ Sky News:12552:v:1:22000:305:306:0:0
KinderNet:12574:h:1:22000:163:92:0:0
Alice:12610:v:1:22000:162:96:0:0
n-tv:12670:v:1:22000:162:96:0:0
-Grand Tour.:12670:v:1:22000:289:290:0:0
+Grand Tourisme:12670:v:1:22000:289:290:0:0
TW1:12692:h:1:22000:166:167:0:0
Eins Extra:12722:h:1:22000:101:102:0:0
Eins Festival:12722:h:1:22000:201:202:0:0
Eins MuXx:12722:h:1:22000:301:302:0:0
-MDR:12722:h:1:22000:401:402:0:0
+MDR:12110:h:1:27500:401:402:0:0
ORB:12722:h:1:22000:501:502:0:0
B1:12722:h:1:22000:601:602:0:0
ARD Online-Kanal:12722:h:1:22000:8191:701:0:0
@@ -113,3 +113,4 @@ MHP test:12604:h:1:22000:5632:8191:0:0
Bloomberg TV Spain:12610:v:1:22000:45:49:0:0
Video Italia:12610:v:1:22000:121:122:0:0
AC 3 promo:12670:v:1:22000:308:256:0:0
+Rtlneu:12188:h:1:27500:163:104:0:0
diff --git a/config.c b/config.c
index ca43ff0..6b5e049 100644
--- a/config.c
+++ b/config.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.c 1.8 2000/06/24 13:43:14 kls Exp $
+ * $Id: config.c 1.15 2000/07/25 16:21:20 kls Exp $
*/
#include "config.h"
@@ -54,7 +54,13 @@ void cKeys::Clear(void)
k->code = 0;
}
-bool cKeys::Load(char *FileName)
+void cKeys::SetDummyValues(void)
+{
+ for (tKey *k = keys; k->type != kNone; k++)
+ k->code = k->type + 1; // '+1' to avoid 0
+}
+
+bool cKeys::Load(const char *FileName)
{
isyslog(LOG_INFO, "loading %s", FileName);
bool result = false;
@@ -150,7 +156,7 @@ unsigned int cKeys::Encode(const char *Command)
{
if (Command != NULL) {
const tKey *k = keys;
- while ((k->type != kNone) && strncmp(k->name, Command, strlen(k->name)) != 0) //XXX why 'strncmp()'???
+ while ((k->type != kNone) && strcmp(k->name, Command) != 0)
k++;
return k->code;
}
@@ -169,6 +175,8 @@ void cKeys::Set(eKeys Key, unsigned int Code)
// -- cChannel ---------------------------------------------------------------
+char *cChannel::buffer = NULL;
+
cChannel::cChannel(void)
{
*name = 0;
@@ -187,7 +195,18 @@ cChannel::cChannel(const cChannel *Channel)
pnr = Channel ? Channel->pnr : 0;
}
-bool cChannel::Parse(char *s)
+const char *cChannel::ToText(cChannel *Channel)
+{
+ asprintf(&buffer, "%s:%d:%c:%d:%d:%d:%d:%d:%d\n", Channel->name, Channel->frequency, Channel->polarization, Channel->diseqc, Channel->srate, Channel->vpid, Channel->apid, Channel->ca, Channel->pnr);
+ return buffer;
+}
+
+const char *cChannel::ToText(void)
+{
+ return ToText(this);
+}
+
+bool cChannel::Parse(const char *s)
{
char *buffer = NULL;
if (9 == sscanf(s, "%a[^:]:%d:%c:%d:%d:%d:%d:%d:%d", &buffer, &frequency, &polarization, &diseqc, &srate, &vpid, &apid, &ca, &pnr)) {
@@ -201,7 +220,7 @@ bool cChannel::Parse(char *s)
bool cChannel::Save(FILE *f)
{
- return fprintf(f, "%s:%d:%c:%d:%d:%d:%d:%d:%d\n", name, frequency, polarization, diseqc, srate, vpid, apid, ca, pnr) > 0;
+ return fprintf(f, ToText()) > 0;
}
bool cChannel::Switch(cDvbApi *DvbApi)
@@ -211,7 +230,7 @@ bool cChannel::Switch(cDvbApi *DvbApi)
if (!DvbApi->Recording()) {
isyslog(LOG_INFO, "switching to channel %d", Index() + 1);
CurrentChannel = Index();
- for (int i = 3; --i;) {
+ for (int i = 3; i--;) {
if (DvbApi->SetChannel(frequency, polarization, diseqc, srate, vpid, apid, ca, pnr))
return true;
esyslog(LOG_ERR, "retrying");
@@ -236,6 +255,8 @@ const char *cChannel::GetChannelName(int i)
// -- cTimer -----------------------------------------------------------------
+char *cTimer::buffer = NULL;
+
cTimer::cTimer(bool Instant)
{
startTime = stopTime = 0;
@@ -253,10 +274,35 @@ cTimer::cTimer(bool Instant)
priority = 99;
lifetime = 99;
*file = 0;
+ summary = NULL;
if (Instant)
snprintf(file, sizeof(file), "@%s", cChannel::GetChannelName(CurrentChannel));
}
+cTimer::~cTimer()
+{
+ delete summary;
+}
+
+cTimer& cTimer::operator= (const cTimer &Timer)
+{
+ memcpy(this, &Timer, sizeof(*this));
+ if (summary)
+ summary = strdup(summary);
+ return *this;
+}
+
+const char *cTimer::ToText(cTimer *Timer)
+{
+ asprintf(&buffer, "%d:%d:%s:%d:%d:%d:%d:%s:%s\n", Timer->active, Timer->channel, PrintDay(Timer->day), Timer->start, Timer->stop, Timer->priority, Timer->lifetime, Timer->file, Timer->summary ? Timer->summary : "");
+ return buffer;
+}
+
+const char *cTimer::ToText(void)
+{
+ return ToText(this);
+}
+
int cTimer::TimeToInt(int t)
{
return (t / 100 * 60 + t % 100) * 60;
@@ -269,7 +315,7 @@ time_t cTimer::Day(time_t t)
return mktime(&d);
}
-int cTimer::ParseDay(char *s)
+int cTimer::ParseDay(const char *s)
{
char *tail;
int d = strtol(s, &tail, 10);
@@ -277,7 +323,7 @@ int cTimer::ParseDay(char *s)
d = 0;
if (tail == s) {
if (strlen(s) == 7) {
- for (char *p = s + 6; p >= s; p--) {
+ for (const char *p = s + 6; p >= s; p--) {
d <<= 1;
d |= (*p != '-');
}
@@ -290,7 +336,7 @@ int cTimer::ParseDay(char *s)
return d;
}
-char *cTimer::PrintDay(int d)
+const char *cTimer::PrintDay(int d)
{
static char buffer[8];
if ((d & 0x80000000) != 0) {
@@ -308,14 +354,20 @@ char *cTimer::PrintDay(int d)
return buffer;
}
-bool cTimer::Parse(char *s)
+bool cTimer::Parse(const char *s)
{
char *buffer1 = NULL;
char *buffer2 = NULL;
- if (8 == sscanf(s, "%d:%d:%a[^:]:%d:%d:%d:%d:%as", &active, &channel, &buffer1, &start, &stop, &priority, &lifetime, &buffer2)) {
+ delete summary;
+ summary = NULL;
+ if (8 <= sscanf(s, "%d:%d:%a[^:]:%d:%d:%d:%d:%a[^:\n]:%a[^\n]", &active, &channel, &buffer1, &start, &stop, &priority, &lifetime, &buffer2, &summary)) {
+ //TODO add more plausibility checks
day = ParseDay(buffer1);
- strncpy(file, buffer2, MaxFileName - 1);
- file[strlen(buffer2)] = 0;
+ int l = strlen(buffer2);
+ if (l >= MaxFileName)
+ l = MaxFileName - 1;
+ strncpy(file, buffer2, l);
+ file[l] = 0;
delete buffer1;
delete buffer2;
return day != 0;
@@ -325,7 +377,7 @@ bool cTimer::Parse(char *s)
bool cTimer::Save(FILE *f)
{
- return fprintf(f, "%d:%d:%s:%d:%d:%d:%d:%s\n", active, channel, PrintDay(day), start, stop, priority, lifetime, file) > 0;
+ return fprintf(f, ToText()) > 0;
}
bool cTimer::IsSingleEvent(void)
@@ -333,10 +385,11 @@ bool cTimer::IsSingleEvent(void)
return (day & 0x80000000) == 0;
}
-bool cTimer::Matches(void)
+bool cTimer::Matches(time_t t)
{
if (active) {
- time_t t = time(NULL);
+ if (t == 0)
+ t = time(NULL);
struct tm now = *localtime(&t);
int weekday = now.tm_wday == 0 ? 6 : now.tm_wday - 1; // we start with monday==0!
int begin = TimeToInt(start);
@@ -393,13 +446,17 @@ void cTimer::SetRecording(bool Recording)
cTimer *cTimer::GetMatch(void)
{
- cTimer *t = (cTimer *)Timers.First();
- while (t) {
- if (!t->recording && t->Matches())
- return t;
- t = (cTimer *)t->Next();
+ time_t t = time(NULL); // all timers must be checked against the exact same time to correctly handle Priority!
+ cTimer *t0 = NULL;
+ cTimer *ti = (cTimer *)Timers.First();
+ while (ti) {
+ if (!ti->recording && ti->Matches(t)) {
+ if (!t0 || ti->priority > t0->priority)
+ t0 = ti;
+ }
+ ti = (cTimer *)ti->Next();
}
- return NULL;
+ return t0;
}
// -- cKeys ------------------------------------------------------------------
diff --git a/config.h b/config.h
index 22099b9..be1c7ec 100644
--- a/config.h
+++ b/config.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.h 1.7 2000/06/24 13:42:32 kls Exp $
+ * $Id: config.h 1.11 2000/07/23 17:17:10 kls Exp $
*/
#ifndef __CONFIG_H
@@ -17,7 +17,7 @@
#include "dvbapi.h"
#include "tools.h"
-#define MaxBuffer 1000
+#define MaxBuffer 10000
enum eKeys { // "Up" and "Down" must be the first two keys!
kUp,
@@ -50,7 +50,8 @@ public:
tKey *keys;
cKeys(void);
void Clear(void);
- bool Load(char *FileName = NULL);
+ void SetDummyValues(void);
+ bool Load(const char *FileName = NULL);
bool Save(void);
unsigned int Encode(const char *Command);
eKeys Get(unsigned int Code);
@@ -58,6 +59,9 @@ public:
};
class cChannel : public cListObject {
+private:
+ static char *buffer;
+ static const char *ToText(cChannel *Channel);
public:
enum { MaxChannelName = 32 }; // 31 chars + terminating 0!
char name[MaxChannelName];
@@ -71,7 +75,8 @@ public:
int pnr;
cChannel(void);
cChannel(const cChannel *Channel);
- bool Parse(char *s);
+ const char *ToText(void);
+ bool Parse(const char *s);
bool Save(FILE *f);
bool Switch(cDvbApi *DvbApi = NULL);
static bool SwitchTo(int i, cDvbApi *DvbApi = NULL);
@@ -81,6 +86,8 @@ public:
class cTimer : public cListObject {
private:
time_t startTime, stopTime;
+ static char *buffer;
+ static const char *ToText(cTimer *Timer);
public:
enum { MaxFileName = 256 };
bool recording;
@@ -93,19 +100,23 @@ public:
int priority;
int lifetime;
char file[MaxFileName];
+ char *summary;
cTimer(bool Instant = false);
- bool Parse(char *s);
+ ~cTimer();
+ cTimer& operator= (const cTimer &Timer);
+ const char *ToText(void);
+ bool Parse(const char *s);
bool Save(FILE *f);
bool IsSingleEvent(void);
- bool Matches(void);
+ bool Matches(time_t t = 0);
time_t StartTime(void);
time_t StopTime(void);
void SetRecording(bool Recording);
static cTimer *GetMatch(void);
static int TimeToInt(int t);
static time_t Day(time_t t);
- static int ParseDay(char *s);
- static char *PrintDay(int d);
+ static int ParseDay(const char *s);
+ static const char *PrintDay(int d);
};
template<class T> class cConfig : public cList<T> {
@@ -117,7 +128,7 @@ private:
cList<T>::Clear();
}
public:
- bool Load(char *FileName)
+ bool Load(const char *FileName)
{
isyslog(LOG_INFO, "loading %s", FileName);
bool result = true;
diff --git a/dvbapi.c b/dvbapi.c
index 58b2d74..a9673a7 100644
--- a/dvbapi.c
+++ b/dvbapi.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: dvbapi.c 1.11 2000/06/24 14:03:19 kls Exp $
+ * $Id: dvbapi.c 1.15 2000/07/21 13:18:02 kls Exp $
*/
#include "dvbapi.h"
@@ -818,8 +818,8 @@ cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName)
// 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;
- write(*OutFile, &dummy, sizeof(dummy));
+ char *dummy = "AV"; // must be "AV" to make the driver go into AV_PES mode!
+ write(*OutFile, dummy, strlen(dummy));
}
cReplayBuffer::~cReplayBuffer()
@@ -1327,8 +1327,8 @@ bool cDvbApi::SetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Sr
struct frontend front;
ioctl(videoDev, VIDIOCGFRONTEND, &front);
unsigned int freq = FrequencyMHz;
- front.ttk = (freq < 11800UL) ? 0 : 1;
- if (freq < 11800UL)
+ front.ttk = (freq < 11700UL) ? 0 : 1;
+ if (freq < 11700UL)
freq -= 9750UL;
else
freq -= 10600UL;
@@ -1337,7 +1337,7 @@ bool cDvbApi::SetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Sr
front.freq = freq * 1000000UL;
front.diseqc = Diseqc;
front.srate = Srate * 1000;
- front.volt = (Polarization == 'v') ? 0 : 1;
+ front.volt = (Polarization == 'v' || Polarization == 'V') ? 0 : 1;
front.video_pid = Vpid;
front.audio_pid = Apid;
front.fec = 8;
@@ -1569,17 +1569,25 @@ bool cDvbApi::StartReplay(const char *FileName, const char *Title)
Buffer->Stop(); break;
case dvbPauseReplay: 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 dvbFastForward: SetReplayMode(VID_PLAY_NORMAL);
+ case dvbFastForward: SetReplayMode(VID_PLAY_CLEAR_BUFFER);
+ SetReplayMode(VID_PLAY_NORMAL);
FastForward = !FastForward;
FastRewind = Paused = false;
+ Buffer->Clear();
Buffer->SetMode(FastForward ? rmFastForward : rmPlay);
break;
- case dvbFastRewind: SetReplayMode(VID_PLAY_NORMAL);
+ case dvbFastRewind: SetReplayMode(VID_PLAY_CLEAR_BUFFER);
+ SetReplayMode(VID_PLAY_NORMAL);
FastRewind = !FastRewind;
FastForward = Paused = false;
+ Buffer->Clear();
Buffer->SetMode(FastRewind ? rmFastRewind : rmPlay);
break;
case dvbSkip: {
@@ -1592,6 +1600,7 @@ bool cDvbApi::StartReplay(const char *FileName, const char *Title)
Buffer->SkipSeconds(Seconds);
}
}
+ break;
case dvbGetIndex: {
int Current, Total;
Buffer->GetIndex(Current, Total);
diff --git a/menu.c b/menu.c
index aab5fd1..17a9b0b 100644
--- a/menu.c
+++ b/menu.c
@@ -4,10 +4,11 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: menu.c 1.16 2000/05/27 16:13:39 kls Exp $
+ * $Id: menu.c 1.20 2000/07/24 16:25:53 kls Exp $
*/
#include "menu.h"
+#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
@@ -16,7 +17,7 @@
#define MENUTIMEOUT 120 // seconds
-const char *FileNameChars = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789-.# ";
+const char *FileNameChars = " aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789-.#^";
// --- cMenuEditItem ---------------------------------------------------------
@@ -429,7 +430,7 @@ void cMenuEditStrItem::Set(void)
char buf[1000];
if (pos >= 0) {
strncpy(buf, value, pos);
- const char *s = value[pos] != ' ' ? value + pos + 1 : "";
+ const char *s = value[pos] != '^' ? value + pos + 1 : "";
snprintf(buf + pos, sizeof(buf) - pos - 2, "[%c]%s", *(value + pos), s);
SetValue(buf);
}
@@ -455,12 +456,12 @@ eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
{
switch (Key) {
case kLeft: if (pos > 0) {
- if (value[pos] == ' ')
+ if (value[pos] == '^')
value[pos] = 0;
pos--;
}
break;
- case kRight: if (pos < length && value[pos] != ' ') {
+ case kRight: if (pos < length && value[pos] != '^' && (pos < int(strlen(value) - 1) || value[pos] != ' ')) {
if (++pos >= int(strlen(value))) {
value[pos] = ' ';
value[pos + 1] = 0;
@@ -474,7 +475,7 @@ eOSState cMenuEditStrItem::ProcessKey(eKeys Key)
return cMenuEditItem::ProcessKey(Key);
break;
case kOk: if (pos >= 0) {
- if (value[pos] == ' ')
+ if (value[pos] == '^')
value[pos] = 0;
pos = -1;
break;
@@ -707,6 +708,51 @@ eOSState cMenuChannels::ProcessKey(eKeys Key)
return state;
}
+// --- cMenuSummary --------------------------------------------------------
+
+class cMenuSummary : public cOsdMenu {
+public:
+ cMenuSummary(const char *Text);
+ virtual eOSState ProcessKey(eKeys Key);
+ };
+
+cMenuSummary::cMenuSummary(const char *Text)
+:cOsdMenu("Summary")
+{
+ while (*Text) {
+ char line[MenuColumns + 1];
+ char *p = line;
+ const char *b = NULL;
+ *p++ = ' ';
+ while (*Text && p - line < MenuColumns - 2) {
+ if (isspace(*Text))
+ b = Text; // remember the blank
+ if (*Text == '\n')
+ break;
+ *p++ = *Text++;
+ }
+ if (*Text) {
+ if (b && Text - b > 0) {
+ p -= Text - b;
+ Text = b + 1;
+ }
+ else
+ Text++;
+ }
+ *p = 0;
+ Add(new cOsdItem(line, osBack));
+ }
+}
+
+eOSState cMenuSummary::ProcessKey(eKeys Key)
+{
+ eOSState state = cOsdMenu::ProcessKey(Key);
+
+ if (state == osUnknown)
+ state = osContinue;
+ return state;
+}
+
// --- cMenuEditTimer --------------------------------------------------------
class cMenuEditTimer : public cOsdMenu {
@@ -799,6 +845,7 @@ private:
eOSState New(void);
eOSState Del(void);
virtual void Move(int From, int To);
+ eOSState Summary(void);
public:
cMenuTimers(void);
virtual eOSState ProcessKey(eKeys Key);
@@ -880,6 +927,16 @@ void cMenuTimers::Move(int From, int To)
isyslog(LOG_INFO, "timer %d moved to %d", From + 1, To + 1);
}
+eOSState cMenuTimers::Summary(void)
+{
+ if (HasSubMenu() || Count() == 0)
+ return osContinue;
+ cTimer *ti = Timers.Get(Current());
+ if (ti && ti->summary && *ti->summary)
+ return AddSubMenu(new cMenuSummary(ti->summary));
+ return Edit(); // convenience for people not using the Summary feature ;-)
+}
+
eOSState cMenuTimers::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
@@ -888,7 +945,7 @@ eOSState cMenuTimers::ProcessKey(eKeys Key)
switch (Key) {
case kLeft:
case kRight: return Activate(Key == kRight);
- case kOk:
+ case kOk: return Summary();
case kRed: return Edit();
case kGreen: return New();
case kYellow: return Del();
@@ -926,13 +983,14 @@ private:
cRecordings Recordings;
eOSState Play(void);
eOSState Del(void);
+ eOSState Summary(void);
public:
cMenuRecordings(void);
virtual eOSState ProcessKey(eKeys Key);
};
cMenuRecordings::cMenuRecordings(void)
-:cOsdMenu("Recordings", 11, 6)
+:cOsdMenu("Recordings", 9, 6)
{
if (Recordings.Load()) {
cRecording *recording = Recordings.First();
@@ -941,7 +999,7 @@ cMenuRecordings::cMenuRecordings(void)
recording = Recordings.Next(recording);
}
}
- SetHelp("Play", NULL/*XXX"Resume"*/, "Delete");
+ SetHelp("Play", NULL/*XXX"Resume"*/, "Delete", "Summary");
}
eOSState cMenuRecordings::Play(void)
@@ -975,6 +1033,16 @@ eOSState cMenuRecordings::Del(void)
return osContinue;
}
+eOSState cMenuRecordings::Summary(void)
+{
+ if (HasSubMenu() || Count() == 0)
+ return osContinue;
+ cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
+ if (ri && ri->recording->Summary() && *ri->recording->Summary())
+ return AddSubMenu(new cMenuSummary(ri->recording->Summary()));
+ return osContinue;
+}
+
eOSState cMenuRecordings::ProcessKey(eKeys Key)
{
eOSState state = cOsdMenu::ProcessKey(Key);
@@ -984,6 +1052,7 @@ eOSState cMenuRecordings::ProcessKey(eKeys Key)
case kOk:
case kRed: return Play();
case kYellow: return Del();
+ case kBlue: return Summary();
default: break;
}
}
@@ -1061,7 +1130,8 @@ cRecordControl::cRecordControl(cDvbApi *DvbApi, cTimer *Timer)
timer->SetRecording(true);
cChannel::SwitchTo(timer->channel - 1, dvbApi);
cRecording Recording(timer);
- dvbApi->StartRecord(Recording.FileName());
+ if (dvbApi->StartRecord(Recording.FileName()))
+ Recording.WriteSummary();
Interface.DisplayRecording(dvbApi->Index(), true);
}
@@ -1116,7 +1186,7 @@ bool cRecordControls::Start(cTimer *Timer)
}
}
else
- esyslog(LOG_ERR, "ERROR: no free DVB device to record channel %d!", ch);
+ esyslog(LOG_ERR, "ERROR: no free DVB device to record channel %d!", ch + 1);
}
else
esyslog(LOG_ERR, "ERROR: channel %d not defined!", ch + 1);
diff --git a/recording.c b/recording.c
index 2eba90b..e37ccd8 100644
--- a/recording.c
+++ b/recording.c
@@ -4,14 +4,16 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: recording.c 1.8 2000/05/13 16:16:56 kls Exp $
+ * $Id: recording.c 1.12 2000/07/24 16:31:07 kls Exp $
*/
#define _GNU_SOURCE
#include "recording.h"
#include <errno.h>
+#include <fcntl.h>
#include <stdio.h>
#include <string.h>
+#include <unistd.h>
#include "interface.h"
#include "tools.h"
@@ -20,7 +22,9 @@
#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT
#define NAMEFORMAT "%s/%s/" DATAFORMAT
-#define FINDCMD "find %s -type d -name '%s'"
+#define SUMMARYFILESUFFIX "/summary.vdr"
+
+#define FINDCMD "find %s -type d -name '%s' | sort -df"
#define DFCMD "df -m %s"
#define MINDISKSPACE 1024 // MB
@@ -102,21 +106,14 @@ void AssertFreeDiskSpace(void)
// --- cRecording ------------------------------------------------------------
-cRecording::cRecording(const char *Name, time_t Start, int Priority, int LifeTime)
-{
- titleBuffer = NULL;
- fileName = NULL;
- name = strdup(Name);
- start = Start;
- priority = Priority;
- lifetime = LifeTime;
-}
-
cRecording::cRecording(cTimer *Timer)
{
titleBuffer = NULL;
fileName = NULL;
name = strdup(Timer->file);
+ summary = Timer->summary ? strdup(Timer->summary) : NULL;
+ if (summary)
+ strreplace(summary, '|', '\n');
start = Timer->StartTime();
priority = Timer->priority;
lifetime = Timer->lifetime;
@@ -130,6 +127,7 @@ cRecording::cRecording(const char *FileName)
char *p = strrchr(FileName, '/');
name = NULL;
+ summary = NULL;
if (p) {
time_t now = time(NULL);
struct tm t = *localtime(&now); // this initializes the time zone in 't'
@@ -141,7 +139,41 @@ cRecording::cRecording(const char *FileName)
name = new char[p - FileName + 1];
strncpy(name, FileName, p - FileName);
name[p - FileName] = 0;
+ strreplace(name, '_', ' ');
}
+ // read an optional summary file:
+ char *SummaryFileName = NULL;
+ asprintf(&SummaryFileName, "%s%s", fileName, SUMMARYFILESUFFIX);
+ int f = open(SummaryFileName, O_RDONLY);
+ if (f >= 0) {
+ struct stat buf;
+ if (fstat(f, &buf) == 0) {
+ int size = buf.st_size;
+ summary = new char[size + 1]; // +1 for terminating 0
+ if (summary) {
+ int rbytes = read(f, summary, size);
+ if (rbytes >= 0) {
+ summary[rbytes] = 0;
+ if (rbytes != size)
+ esyslog(LOG_ERR, "%s: expected %d bytes but read %d", SummaryFileName, size, rbytes);
+ }
+ else {
+ LOG_ERROR_STR(SummaryFileName);
+ delete summary;
+ summary = NULL;
+ }
+
+ }
+ else
+ esyslog(LOG_ERR, "can't allocate %d byte of memory for summary file '%s'", size + 1, SummaryFileName);
+ close(f);
+ }
+ else
+ LOG_ERROR_STR(SummaryFileName);
+ }
+ else if (errno != ENOENT)
+ LOG_ERROR_STR(SummaryFileName);
+ delete SummaryFileName;
}
}
@@ -150,6 +182,7 @@ cRecording::~cRecording()
delete titleBuffer;
delete fileName;
delete name;
+ delete summary;
}
const char *cRecording::FileName(void)
@@ -157,6 +190,8 @@ const char *cRecording::FileName(void)
if (!fileName) {
struct tm *t = localtime(&start);
asprintf(&fileName, NAMEFORMAT, BaseDir, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime);
+ if (fileName)
+ strreplace(fileName, ' ', '_');
}
return fileName;
}
@@ -166,10 +201,10 @@ const char *cRecording::Title(char Delimiter)
delete titleBuffer;
titleBuffer = NULL;
struct tm *t = localtime(&start);
- asprintf(&titleBuffer, "%02d.%02d.%04d%c%02d:%02d%c%s",
+ asprintf(&titleBuffer, "%02d.%02d.%02d%c%02d:%02d%c%s",
t->tm_mday,
t->tm_mon + 1,
- t->tm_year + 1900,
+ t->tm_year % 100,
Delimiter,
t->tm_hour,
t->tm_min,
@@ -178,6 +213,24 @@ const char *cRecording::Title(char Delimiter)
return titleBuffer;
}
+bool cRecording::WriteSummary(void)
+{
+ if (summary) {
+ char *SummaryFileName = NULL;
+ asprintf(&SummaryFileName, "%s%s", fileName, SUMMARYFILESUFFIX);
+ FILE *f = fopen(SummaryFileName, "w");
+ if (f) {
+ if (fputs(summary, f) < 0)
+ LOG_ERROR_STR(SummaryFileName);
+ fclose(f);
+ }
+ else
+ LOG_ERROR_STR(SummaryFileName);
+ delete SummaryFileName;
+ }
+ return true;
+}
+
bool cRecording::Delete(void)
{
bool result = true;
diff --git a/recording.h b/recording.h
index eeea560..e501af8 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.6 2000/04/24 09:45:49 kls Exp $
+ * $Id: recording.h 1.7 2000/07/23 19:06:14 kls Exp $
*/
#ifndef __RECORDING_H
@@ -22,16 +22,18 @@ private:
char *titleBuffer;
char *fileName;
char *name;
+ char *summary;
public:
time_t start;
int priority;
int lifetime;
- cRecording(const char *Name, time_t Start, int Priority, int LifeTime);
cRecording(cTimer *Timer);
cRecording(const char *FileName);
~cRecording();
const char *FileName(void);
const char *Title(char Delimiter = ' ');
+ const char *Summary(void) { return summary; }
+ bool WriteSummary(void);
bool Delete(void);
// Changes the file name so that it will no longer be visible in the "Recordings" menu
// Returns false in case of error
diff --git a/remote.c b/remote.c
index e3786c5..931cfd3 100644
--- a/remote.c
+++ b/remote.c
@@ -6,7 +6,7 @@
*
* Ported to LIRC by Carsten Koch <Carsten.Koch@icem.de> 2000-06-16.
*
- * $Id: remote.c 1.9 2000/07/15 12:19:50 kls Exp $
+ * $Id: remote.c 1.10 2000/07/15 16:34:35 kls Exp $
*/
#include "remote.h"
@@ -365,17 +365,20 @@ cRcIoLIRC::~cRcIoLIRC()
const char *cRcIoLIRC::ReceiveString(void)
{
+ char buf[LIRC_BUFFER_SIZE];
+
while (InputAvailable(true)) {
if (read(f, buf, sizeof(buf)) > 21) {
- const int repeat = 10 * (buf[17] - '0') + (buf[18] - '0');
const int now = time_ms();
+ int repeat;
+ sscanf(buf, "%*s %x %7s", &repeat, keyName); // '7' in '%7s' is LIRC_KEY_BUF-1!
if (repeat == 0) {
firstTime = lastTime = now;
- return buf + 20;
+ return keyName;
}
else if ((now > firstTime + REPEATDELAY) && (now > lastTime + REPEATLIMIT)) {
lastTime = now;
- return buf + 20;
+ return keyName;
}
}
}
@@ -384,6 +387,7 @@ const char *cRcIoLIRC::ReceiveString(void)
void cRcIoLIRC::Flush(int WaitSeconds)
{
+ char buf[LIRC_BUFFER_SIZE];
time_t t0 = time(NULL);
for (;;) {
diff --git a/remote.h b/remote.h
index 38961b9..7b94ac7 100644
--- a/remote.h
+++ b/remote.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: remote.h 1.6 2000/06/24 15:52:56 kls Exp $
+ * $Id: remote.h 1.7 2000/07/15 16:32:43 kls Exp $
*/
#ifndef __REMOTE_H
@@ -75,9 +75,9 @@ public:
class cRcIoLIRC : public cRcIoBase {
private:
- enum { LIRC_BUFFER_SIZE = 128 };
+ enum { LIRC_KEY_BUF = 8, LIRC_BUFFER_SIZE = 128 };
int f;
- char buf[LIRC_BUFFER_SIZE];
+ char keyName[LIRC_KEY_BUF];
const char *ReceiveString(void);
public:
cRcIoLIRC(char *DeviceName);
diff --git a/svdrp.c b/svdrp.c
new file mode 100644
index 0000000..6873d16
--- /dev/null
+++ b/svdrp.c
@@ -0,0 +1,620 @@
+/*
+ * svdrp.c: Simple Video Disk Recorder Protocol
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * The "Simple Video Disk Recorder Protocol" (SVDRP) was inspired
+ * by the "Simple Mail Transfer Protocol" (SMTP) and is fully ASCII
+ * text based. Therefore you can simply 'telnet' to your VDR port
+ * 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.2 2000/07/24 16:43:51 kls Exp $
+ */
+
+#define _GNU_SOURCE
+
+#include "svdrp.h"
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include "config.h"
+#include "interface.h"
+#include "tools.h"
+
+// --- cSocket ---------------------------------------------------------------
+
+cSocket::cSocket(int Port, int Queue)
+{
+ port = Port;
+ sock = -1;
+}
+
+cSocket::~cSocket()
+{
+ Close();
+}
+
+void cSocket::Close(void)
+{
+ if (sock >= 0) {
+ close(sock);
+ sock = -1;
+ }
+}
+
+bool cSocket::Open(void)
+{
+ if (sock < 0) {
+ // create socket:
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ if (sock < 0) {
+ LOG_ERROR;
+ port = 0;
+ return false;
+ }
+ struct sockaddr_in name;
+ name.sin_family = AF_INET;
+ name.sin_port = htons(port);
+ name.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (bind(sock, (struct sockaddr *)&name, sizeof(name)) < 0) {
+ LOG_ERROR;
+ Close();
+ return false;
+ }
+ // make it non-blocking:
+ int oldflags = fcntl(sock, F_GETFL, 0);
+ if (oldflags < 0) {
+ LOG_ERROR;
+ return false;
+ }
+ oldflags |= O_NONBLOCK;
+ if (fcntl(sock, F_SETFL, oldflags) < 0) {
+ LOG_ERROR;
+ return false;
+ }
+ // listen to the socket:
+ if (listen(sock, queue) < 0) {
+ LOG_ERROR;
+ return false;
+ }
+ }
+ return true;
+}
+
+int cSocket::Accept(void)
+{
+ if (Open()) {
+ struct sockaddr_in clientname;
+ uint size = sizeof(clientname);
+ int newsock = accept(sock, (struct sockaddr *)&clientname, &size);
+ if (newsock > 0)
+ isyslog(LOG_INFO, "connect from %s, port %hd", inet_ntoa(clientname.sin_addr), ntohs(clientname.sin_port));
+ else if (errno != EINTR)
+ LOG_ERROR;
+ return newsock;
+ }
+ return -1;
+}
+
+// --- cSVDRP ----------------------------------------------------------------
+
+#define MAXCMDBUFFER 10000
+#define MAXHELPTOPIC 10
+
+const char *HelpPages[] = {
+ "CHAN [ + | - | <number> | <name> ]\n"
+ " Switch channel up, down or to the given channel number or name.\n"
+ " Without option (or after successfully switching to the channel)\n"
+ " it returns the current channel number and name.",
+ "DELC <number>\n"
+ " Delete channel.",
+ "DELT <number>\n"
+ " Delete timer.",
+ "HELP [ <topic> ]\n"
+ " The HELP command gives help info.",
+ "LSTC [ <number> | <name> ]\n"
+ " List channels. Without option, all channels are listed. Otherwise\n"
+ " only the given channel is listed. If a name is given, all channels\n"
+ " containing the given string as part of their name are listed.",
+ "LSTT [ <number> ]\n"
+ " List timers. Without option, all timers are listed. Otherwise\n"
+ " only the given timer is listed.",
+ "MODC <number> <settings>\n"
+ " Modify a channel. Settings must be in the same format as returned\n"
+ " by the LSTC command.",
+ "MODT <number> on | off | <settings>\n"
+ " Modify a timer. Settings must be in the same format as returned\n"
+ " by the LSTT command. The special keywords 'on' and 'off' can be\n"
+ " used to easily activate or deactivate a timer.",
+ "MOVC <number> <to>\n"
+ " Move a channel to a new position.",
+ "MOVT <number> <to>\n"
+ " Move a timer to a new position.",
+ "NEWC <settings>\n"
+ " Create a new channel. Settings must be in the same format as returned\n"
+ " by the LSTC command.",
+ "NEWT <settings>\n"
+ " Create a new timer. Settings must be in the same format as returned\n"
+ " by the LSTT command.",
+ "QUIT\n"
+ " Exit vdr (SVDRP).\n"
+ " You can also hit Ctrl-D to exit.",
+ NULL
+ };
+
+/* SVDRP Reply Codes:
+
+ 214 Help message
+ 220 VDR service ready
+ 221 VDR service closing transmission channel
+ 250 Requested VDR action okay, completed
+ 451 Requested action aborted: local error in processing
+ 500 Syntax error, command unrecognized
+ 501 Syntax error in parameters or arguments
+ 502 Command not implemented
+ 504 Command parameter not implemented
+ 550 Requested action not taken
+ 554 Transaction failed
+
+*/
+
+const char *GetHelpTopic(const char *HelpPage)
+{
+ static char topic[MAXHELPTOPIC];
+ const char *q = HelpPage;
+ while (*q) {
+ if (isspace(*q)) {
+ uint n = q - HelpPage;
+ if (n >= sizeof(topic))
+ n = sizeof(topic) - 1;
+ strncpy(topic, HelpPage, n);
+ topic[n] = 0;
+ return topic;
+ }
+ q++;
+ }
+ return NULL;
+}
+
+const char *GetHelpPage(const char *Cmd)
+{
+ const char **p = HelpPages;
+ while (*p) {
+ const char *t = GetHelpTopic(*p);
+ if (strcasecmp(Cmd, t) == 0)
+ return *p;
+ p++;
+ }
+ return NULL;
+}
+
+cSVDRP::cSVDRP(int Port)
+:socket(Port)
+{
+ filedes = -1;
+ isyslog(LOG_INFO, "SVDRP listening on port %d", Port);
+}
+
+cSVDRP::~cSVDRP()
+{
+ Close();
+}
+
+void cSVDRP::Close(void)
+{
+ if (filedes >= 0) {
+ //TODO how can we get the *full* hostname?
+ char buffer[MAXCMDBUFFER];
+ gethostname(buffer, sizeof(buffer));
+ Reply(221, "%s closing connection", buffer);
+ isyslog(LOG_INFO, "closing connection"); //TODO store IP#???
+ close(filedes);
+ filedes = -1;
+ }
+}
+
+bool cSVDRP::Send(const char *s, int length)
+{
+ if (length < 0)
+ length = strlen(s);
+ int wbytes = write(filedes, s, length);
+ if (wbytes == length)
+ return true;
+ if (wbytes < 0)
+ LOG_ERROR;
+ else //XXX while...???
+ esyslog(LOG_ERR, "Wrote %d bytes to client while expecting %d\n", wbytes, length);
+ return false;
+}
+
+void cSVDRP::Reply(int Code, const char *fmt, ...)
+{
+ if (filedes >= 0) {
+ if (Code != 0) {
+ va_list ap;
+ va_start(ap, fmt);
+ char *buffer;
+ vasprintf(&buffer, fmt, ap);
+ char *nl = strchr(buffer, '\n');
+ if (Code > 0 && nl && *(nl + 1)) // trailing newlines don't count!
+ Code = -Code;
+ char number[16];
+ sprintf(number, "%03d%c", abs(Code), Code < 0 ? '-' : ' ');
+ const char *s = buffer;
+ while (s && *s) {
+ const char *n = strchr(s, '\n');
+ if (!(Send(number) && Send(s, n ? n - s : -1) && Send("\r\n"))) {
+ Close();
+ break;
+ }
+ s = n ? n + 1 : NULL;
+ }
+ delete buffer;
+ va_end(ap);
+ }
+ else {
+ Reply(451, "Zero return code - looks like a programming error!");
+ esyslog(LOG_ERR, "SVDRP: zero return code!");
+ }
+ }
+}
+
+void cSVDRP::CmdChan(const char *Option)
+{
+ if (*Option) {
+ int n = -1;
+ if (isnumber(Option)) {
+ int o = strtol(Option, NULL, 10) - 1;
+ if (o >= 0 && o < Channels.Count())
+ n = o;
+ }
+ else if (strcmp(Option, "-") == 0) {
+ n = CurrentChannel;
+ if (CurrentChannel > 0)
+ n--;
+ }
+ else if (strcmp(Option, "+") == 0) {
+ n = CurrentChannel;
+ if (CurrentChannel < Channels.Count() - 1)
+ n++;
+ }
+ else {
+ int i = 0;
+ cChannel *channel;
+ while ((channel = Channels.Get(i)) != NULL) {
+ if (strcasecmp(channel->name, Option) == 0) {
+ n = i;
+ break;
+ }
+ i++;
+ }
+ }
+ if (n < 0) {
+ Reply(501, "Undefined channel \"%s\"", Option);
+ return;
+ }
+ if (Interface.Recording()) {
+ Reply(550, "Can't switch channel, interface is recording");
+ return;
+ }
+ cChannel *channel = Channels.Get(n);
+ if (channel) {
+ if (!channel->Switch()) {
+ Reply(554, "Error switching to channel \"%d\"", channel->Index() + 1);
+ return;
+ }
+ }
+ else {
+ Reply(550, "Unable to find channel \"%s\"", Option);
+ return;
+ }
+ }
+ cChannel *channel = Channels.Get(CurrentChannel);
+ if (channel)
+ Reply(250, "%d %s", CurrentChannel + 1, channel->name);
+ else
+ Reply(550, "Unable to find channel \"%d\"", CurrentChannel);
+}
+
+void cSVDRP::CmdDelc(const char *Option)
+{
+ //TODO combine this with menu action (timers must be updated)
+ Reply(502, "DELC not yet implemented");
+}
+
+void cSVDRP::CmdDelt(const char *Option)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
+ if (timer) {
+ if (!timer->recording) {
+ Timers.Del(timer);
+ Timers.Save();
+ isyslog(LOG_INFO, "timer %s deleted", Option);
+ Reply(250, "Timer \"%s\" deleted", Option);
+ }
+ else
+ Reply(550, "Timer \"%s\" is recording", Option);
+ }
+ else
+ Reply(501, "Timer \"%s\" not defined", Option);
+ }
+ else
+ Reply(501, "Error in timer number \"%s\"", Option);
+ }
+ else
+ Reply(501, "Missing timer number");
+}
+
+void cSVDRP::CmdHelp(const char *Option)
+{
+ if (*Option) {
+ const char *hp = GetHelpPage(Option);
+ if (hp)
+ Reply(214, hp);
+ else {
+ Reply(504, "HELP topic \"%s\" unknown", Option);
+ return;
+ }
+ }
+ else {
+ Reply(-214, "This is VDR version 0.6"); //XXX dynamically insert version number
+ Reply(-214, "Topics:");
+ const char **hp = HelpPages;
+ while (*hp) {
+ //TODO multi-column???
+ const char *topic = GetHelpTopic(*hp);
+ if (topic)
+ Reply(-214, " %s", topic);
+ hp++;
+ }
+ Reply(-214, "To report bugs in the implementation send email to");
+ Reply(-214, " vdr-bugs@cadsoft.de");
+ }
+ Reply(214, "End of HELP info");
+}
+
+void cSVDRP::CmdLstc(const char *Option)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ cChannel *channel = Channels.Get(strtol(Option, NULL, 10) - 1);
+ if (channel)
+ Reply(250, "%d %s", channel->Index() + 1, channel->ToText());
+ else
+ Reply(501, "Channel \"%s\" not defined", Option);
+ }
+ else {
+ int i = 0;
+ cChannel *next = NULL;
+ while (i < Channels.Count()) {
+ cChannel *channel = Channels.Get(i);
+ if (channel) {
+ if (strcasestr(channel->name, Option)) {
+ if (next)
+ Reply(-250, "%d %s", next->Index() + 1, next->ToText());
+ next = channel;
+ }
+ }
+ else {
+ Reply(501, "Channel \"%d\" not found", i + 1);
+ return;
+ }
+ i++;
+ }
+ if (next)
+ Reply(250, "%d %s", next->Index() + 1, next->ToText());
+ }
+ }
+ else {
+ for (int i = 0; i < Channels.Count(); i++) {
+ cChannel *channel = Channels.Get(i);
+ if (channel)
+ Reply(i < Channels.Count() - 1 ? -250 : 250, "%d %s", channel->Index() + 1, channel->ToText());
+ else
+ Reply(501, "Channel \"%d\" not found", i + 1);
+ }
+ }
+}
+
+void cSVDRP::CmdLstt(const char *Option)
+{
+ if (*Option) {
+ if (isnumber(Option)) {
+ cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1);
+ if (timer)
+ Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
+ else
+ Reply(501, "Timer \"%s\" not defined", Option);
+ }
+ else
+ Reply(501, "Error in timer number \"%s\"", Option);
+ }
+ else {
+ for (int i = 0; i < Timers.Count(); i++) {
+ cTimer *timer = Timers.Get(i);
+ if (timer)
+ Reply(i < Timers.Count() - 1 ? -250 : 250, "%d %s", timer->Index() + 1, timer->ToText());
+ else
+ Reply(501, "Timer \"%d\" not found", i + 1);
+ }
+ }
+}
+
+void cSVDRP::CmdModc(const char *Option)
+{
+ if (*Option) {
+ char *tail;
+ int n = strtol(Option, &tail, 10);
+ if (tail && tail != Option) {
+ tail = skipspace(tail);
+ cChannel *channel = Channels.Get(n - 1);
+ if (channel) {
+ cChannel c = *channel;
+ if (!c.Parse(tail)) {
+ Reply(501, "Error in channel settings");
+ return;
+ }
+ *channel = c;
+ Channels.Save();
+ isyslog(LOG_INFO, "channel %d modified", channel->Index() + 1);
+ Reply(250, "%d %s", channel->Index() + 1, channel->ToText());
+ }
+ else
+ Reply(501, "Channel \"%d\" not defined", n);
+ }
+ else
+ Reply(501, "Error in channel number");
+ }
+ else
+ Reply(501, "Missing channel settings");
+}
+
+void cSVDRP::CmdModt(const char *Option)
+{
+ if (*Option) {
+ char *tail;
+ int n = strtol(Option, &tail, 10);
+ if (tail && tail != Option) {
+ tail = skipspace(tail);
+ cTimer *timer = Timers.Get(n - 1);
+ if (timer) {
+ cTimer t = *timer;
+ if (strcasecmp(tail, "ON") == 0)
+ t.active = 1;
+ else if (strcasecmp(tail, "OFF") == 0)
+ t.active = 0;
+ else if (!t.Parse(tail)) {
+ Reply(501, "Error in timer settings");
+ return;
+ }
+ *timer = t;
+ Timers.Save();
+ isyslog(LOG_INFO, "timer %d modified (%s)", timer->Index() + 1, timer->active ? "active" : "inactive");
+ Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
+ }
+ else
+ Reply(501, "Timer \"%d\" not defined", n);
+ }
+ else
+ Reply(501, "Error in timer number");
+ }
+ else
+ Reply(501, "Missing timer settings");
+}
+
+void cSVDRP::CmdMovc(const char *Option)
+{
+ //TODO combine this with menu action (timers must be updated)
+ Reply(502, "MOVC not yet implemented");
+}
+
+void cSVDRP::CmdMovt(const char *Option)
+{
+ //TODO combine this with menu action
+ Reply(502, "MOVT not yet implemented");
+}
+
+void cSVDRP::CmdNewc(const char *Option)
+{
+ if (*Option) {
+ cChannel *channel = new cChannel;
+ if (channel->Parse(Option)) {
+ Channels.Add(channel);
+ Channels.Save();
+ isyslog(LOG_INFO, "channel %d added", channel->Index() + 1);
+ Reply(250, "%d %s", channel->Index() + 1, channel->ToText());
+ }
+ else
+ Reply(501, "Error in channel settings");
+ }
+ else
+ Reply(501, "Missing channel settings");
+}
+
+void cSVDRP::CmdNewt(const char *Option)
+{
+ if (*Option) {
+ cTimer *timer = new cTimer;
+ if (timer->Parse(Option)) {
+ Timers.Add(timer);
+ Timers.Save();
+ isyslog(LOG_INFO, "timer %d added", timer->Index() + 1);
+ Reply(250, "%d %s", timer->Index() + 1, timer->ToText());
+ }
+ else
+ Reply(501, "Error in timer settings");
+ }
+ else
+ Reply(501, "Missing timer settings");
+}
+
+#define CMD(c) (strcasecmp(Cmd, c) == 0)
+
+void cSVDRP::Execute(char *Cmd)
+{
+ // skip leading whitespace:
+ Cmd = skipspace(Cmd);
+ // find the end of the command word:
+ char *s = Cmd;
+ while (*s && !isspace(*s))
+ s++;
+ *s++ = 0;
+ if (CMD("CHAN")) CmdChan(s);
+ else if (CMD("DELC")) CmdDelc(s);
+ else if (CMD("DELT")) CmdDelt(s);
+ else if (CMD("HELP")) CmdHelp(s);
+ else if (CMD("LSTC")) CmdLstc(s);
+ else if (CMD("LSTT")) CmdLstt(s);
+ else if (CMD("MODC")) CmdModc(s);
+ else if (CMD("MODT")) CmdModt(s);
+ else if (CMD("MOVC")) CmdMovc(s);
+ else if (CMD("MOVT")) CmdMovt(s);
+ else if (CMD("NEWC")) CmdNewc(s);
+ else if (CMD("NEWT")) CmdNewt(s);
+ else if (CMD("QUIT")
+ || CMD("\x04")) Close();
+ else Reply(500, "Command unrecognized: \"%s\"", Cmd);
+}
+
+void cSVDRP::Process(void)
+{
+ bool SendGreeting = filedes < 0;
+
+ if (filedes >= 0 || (filedes = socket.Accept()) >= 0) {
+ char buffer[MAXCMDBUFFER];
+ if (SendGreeting) {
+ //TODO how can we get the *full* hostname?
+ gethostname(buffer, sizeof(buffer));
+ time_t now = time(NULL);
+ Reply(220, "%s SVDRP VideoDiskRecorder 0.6; %s", buffer, ctime(&now));//XXX dynamically insert version number
+ }
+ int rbytes = readstring(filedes, buffer, sizeof(buffer) - 1);
+ if (rbytes > 0) {
+ //XXX overflow check???
+ // strip trailing whitespace:
+ while (rbytes > 0 && strchr(" \t\r\n", buffer[rbytes - 1]))
+ buffer[--rbytes] = 0;
+ // make sure the string is terminated:
+ buffer[rbytes] = 0;
+ // showtime!
+ Execute(buffer);
+ }
+ else if (rbytes < 0)
+ Close();
+ }
+}
+
+//TODO timeout???
+//TODO more than one connection???
diff --git a/svdrp.h b/svdrp.h
new file mode 100644
index 0000000..c638542
--- /dev/null
+++ b/svdrp.h
@@ -0,0 +1,52 @@
+/*
+ * svdrp.h: Simple Video Disk Recorder Protocol
+ *
+ * See the main source file 'vdr.c' for copyright information and
+ * how to reach the author.
+ *
+ * $Id: svdrp.h 1.1 2000/07/23 14:49:30 kls Exp $
+ */
+
+#ifndef __SVDRP_H
+#define __SVDRP_H
+
+class cSocket {
+private:
+ int port;
+ int sock;
+ int queue;
+ void Close(void);
+public:
+ cSocket(int Port, int Queue = 1);
+ ~cSocket();
+ bool Open(void);
+ int Accept(void);
+ };
+
+class cSVDRP {
+private:
+ cSocket socket;
+ int filedes;
+ void Close(void);
+ bool Send(const char *s, int length = -1);
+ void Reply(int Code, const char *fmt, ...);
+ void CmdChan(const char *Option);
+ void CmdDelc(const char *Option);
+ void CmdDelt(const char *Option);
+ void CmdHelp(const char *Option);
+ void CmdLstc(const char *Option);
+ void CmdLstt(const char *Option);
+ void CmdModc(const char *Option);
+ void CmdModt(const char *Option);
+ void CmdMovc(const char *Option);
+ void CmdMovt(const char *Option);
+ void CmdNewc(const char *Option);
+ void CmdNewt(const char *Option);
+ void Execute(char *Cmd);
+public:
+ cSVDRP(int Port);
+ ~cSVDRP();
+ void Process(void);
+ };
+
+#endif //__SVDRP_H
diff --git a/timers.conf b/timers.conf
index a5dcb9a..659faaa 100644
--- a/timers.conf
+++ b/timers.conf
@@ -1,10 +1,9 @@
-1:15:MTWTF--:1828:1901:10:5:nano
-1:10:-T-----:2058:2202:99:10:Quarks
-1:5:-T-----:2100:2205:99:10:RudisSuchmaschine
-0:3:---T---:2211:2300:99:10:Switch
-1:15:-----S-:1358:1435:99:7:Neues
-1:1:-----S-:1445:1600:99:30:Hammerman
-1:2:-----S-:2200:2350:99:30:Wochenshow
-1:11:------S:2058:2120:99:10:Centauri
-1:14:20:1920:2035:99:30:SamtUndSeide
-1:10:---T---:2158:2250:99:99:DiePlaneten
+1:10:-T-----:2058:2202:99:10:Quarks:
+1:5:-T-----:2100:2205:99:10:RudisSuchmaschine:
+1:10:---T---:2158:2250:99:99:DiePlaneten:
+1:3:---T---:2211:2300:99:10:Switch:
+1:15:-----S-:1358:1435:99:7:Neues:
+1:1:-----S-:1445:1600:99:30:Hammerman:
+0:2:-----S-:2200:2350:99:30:Wochenshow:
+1:11:------S:2058:2120:99:10:Centauri:
+0:15:MTWTF--:1828:1901:10:5:nano:
diff --git a/tools.c b/tools.c
index c0eb2ec..998d91b 100644
--- a/tools.c
+++ b/tools.c
@@ -4,11 +4,12 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.c 1.8 2000/06/24 15:26:15 kls Exp $
+ * $Id: tools.c 1.10 2000/07/23 13:16:54 kls Exp $
*/
#define _GNU_SOURCE
#include "tools.h"
+#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <signal.h>
@@ -58,6 +59,26 @@ bool readint(int filedes, int &n)
return DataAvailable(filedes) && read(filedes, &n, sizeof(n)) == sizeof(n);
}
+int readstring(int filedes, char *buffer, int size, bool wait = false)
+{
+ int rbytes = 0;
+
+ while (DataAvailable(filedes, wait)) {
+ int n = read(filedes, buffer + rbytes, size - rbytes);
+ if (n == 0)
+ break; // EOF
+ if (n < 0) {
+ LOG_ERROR;
+ break;
+ }
+ rbytes += n;
+ if (rbytes == size)
+ break;
+ wait = false;
+ }
+ return rbytes;
+}
+
void purge(int filedes)
{
while (DataAvailable(filedes))
@@ -76,6 +97,25 @@ char *readline(FILE *f)
return NULL;
}
+char *strreplace(char *s, char c1, char c2)
+{
+ char *p = s;
+
+ while (*p) {
+ if (*p == c1)
+ *p = c2;
+ p++;
+ }
+ return s;
+}
+
+char *skipspace(char *s)
+{
+ while (*s && isspace(*s))
+ s++;
+ return s;
+}
+
int time_ms(void)
{
static time_t t0 = 0;
@@ -95,6 +135,16 @@ void delay_ms(int ms)
;
}
+bool isnumber(const char *s)
+{
+ while (*s) {
+ if (!isdigit(*s))
+ return false;
+ s++;
+ }
+ return true;
+}
+
bool MakeDirs(const char *FileName, bool IsDirectory)
{
bool result = true;
diff --git a/tools.h b/tools.h
index db55983..ecf42be 100644
--- a/tools.h
+++ b/tools.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.h 1.8 2000/06/24 15:25:00 kls Exp $
+ * $Id: tools.h 1.10 2000/07/23 13:16:37 kls Exp $
*/
#ifndef __TOOLS_H
@@ -35,10 +35,14 @@ void writechar(int filedes, char c);
void writeint(int filedes, int n);
char readchar(int filedes);
bool readint(int filedes, int &n);
+int readstring(int filedes, char *buffer, int size, bool wait = false);
void purge(int filedes);
char *readline(FILE *f);
+char *strreplace(char *s, char c1, char c2);
+char *skipspace(char *s);
int time_ms(void);
void delay_ms(int ms);
+bool isnumber(const char *s);
bool MakeDirs(const char *FileName, bool IsDirectory = false);
bool RemoveFileOrDir(const char *FileName);
bool CheckProcess(pid_t pid);
diff --git a/vdr.c b/vdr.c
index 4c1dce0..626e6dc 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,15 +22,19 @@
*
* The project's page is at http://www.cadsoft.de/people/kls/vdr
*
- * $Id: vdr.c 1.20 2000/07/15 11:45:05 kls Exp $
+ * $Id: vdr.c 1.23 2000/07/23 15:36:43 kls Exp $
*/
+#include <getopt.h>
#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
#include "config.h"
#include "dvbapi.h"
#include "interface.h"
#include "menu.h"
#include "recording.h"
+#include "svdrp.h"
#include "tools.h"
#ifdef REMOTE_KBD
@@ -50,15 +54,83 @@ void SignalHandler(int signum)
int main(int argc, char *argv[])
{
+ // Command line options:
+
+#define DEFAULTSVDRPPORT 2001
+
+ int SVDRPport = DEFAULTSVDRPPORT;
+ bool DaemonMode = false;
+
+ static struct option long_options[] = {
+ { "daemon", no_argument, NULL, 'd' },
+ { "help", no_argument, NULL, 'h' },
+ { "port", required_argument, NULL, 'p' },
+ { 0 }
+ };
+
+ int c;
+ int option_index = 0;
+ while ((c = getopt_long(argc, argv, "dhp:", long_options, &option_index)) != -1) {
+ switch (c) {
+ case 'd': DaemonMode = true; break;
+ case 'h': printf("Usage: vdr [OPTION]\n\n"
+ " -h, --help display this help and exit\n"
+ " -d, --daemon run in daemon mode\n"
+ " -p PORT, --port=PORT use PORT for SVDRP ('0' turns off SVDRP)\n"
+ "\n"
+ "Report bugs to <vdr-bugs@cadsoft.de>\n"
+ );
+ return 0;
+ break;
+ case 'p': if (isnumber(optarg))
+ SVDRPport = strtol(optarg, NULL, 10);
+ else {
+ fprintf(stderr, "vdr: invalid port number: %s\n", optarg);
+ return 1;
+ }
+ break;
+ default: abort();
+ }
+ }
+
+ // Log file:
+
openlog("vdr", LOG_PID | LOG_CONS, LOG_USER);
+
+ // Daemon mode:
+
+ if (DaemonMode) {
+#ifndef DEBUG_OSD
+ pid_t pid = fork();
+ if (pid < 0) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ esyslog(LOG_ERR, strerror(errno));
+ return 1;
+ }
+ if (pid != 0)
+ return 0; // initial program immediately returns
+ fclose(stdin);
+ fclose(stdout);
+ fclose(stderr);
+#else
+ fprintf(stderr, "vdr: can't run in daemon mode with DEBUG_OSD on!\n");
+ abort();
+#endif
+ }
isyslog(LOG_INFO, "started");
+ // DVB interfaces:
+
if (!cDvbApi::Init())
return 1;
+ // Configuration data:
+
Channels.Load("channels.conf");
Timers.Load("timers.conf");
-#ifndef REMOTE_LIRC
+#ifdef REMOTE_LIRC
+ Keys.SetDummyValues();
+#else
if (!Keys.Load(KEYS_CONF))
Interface.LearnKeys();
#endif
@@ -66,10 +138,15 @@ int main(int argc, char *argv[])
cChannel::SwitchTo(CurrentChannel);
+ // Signal handlers:
+
if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN);
if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN);
if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN);
+ // Main program loop:
+
+ cSVDRP *SVDRP = SVDRPport ? new cSVDRP(SVDRPport) : NULL;
cMenuMain *Menu = NULL;
cReplayControl *ReplayControl = NULL;
int dcTime = 0, dcNumber = 0;
@@ -155,10 +232,13 @@ int main(int argc, char *argv[])
default: break;
}
}
+ if (SVDRP)
+ SVDRP->Process();//TODO lock menu vs. SVDRP?
}
isyslog(LOG_INFO, "caught signal %d", Interrupted);
delete Menu;
delete ReplayControl;
+ delete SVDRP;
cDvbApi::Cleanup();
isyslog(LOG_INFO, "exiting");
closelog();