1 | #!/usr/bin/perl -w |
---|
2 | # Name: mythical |
---|
3 | # Version: 0.1 |
---|
4 | # Release: 1 |
---|
5 | # Summary: Generate an iCalendar file of upcoming MythTV recordings |
---|
6 | # License: GPL - use at your own risk etc. |
---|
7 | # Copyright: (C) 2008 - David Greenhouse <daveg at dgnode dot screaming dot net> |
---|
8 | # Description: |
---|
9 | # Similar to mythtv/contrib/misc_status_info/myth_upcoming_recordings.pl |
---|
10 | # but generates an iCalendar (RFC2445) file for use with desktop calendar |
---|
11 | # applications such as KOrganizer. |
---|
12 | # Why? |
---|
13 | # The information is already available within MythTV frontend and MythWeb but is |
---|
14 | # a natural fit for a desktop calendar application. With the data in iCalendar (RFC2445) |
---|
15 | # format it becomes very portable either by direct access or via synchronisation such |
---|
16 | # as SyncML. May even get my Palm to run as a IR remote and synchronise the recording |
---|
17 | # schedule via OBEX! |
---|
18 | # Usage: |
---|
19 | # # MythTV class gripes about UPnP missing so redirect stderr if run by cron etc. |
---|
20 | # mythical.pl 2>/dev/null >~/mythical.ics |
---|
21 | # TODO: |
---|
22 | # UTF-8. |
---|
23 | # Run-time options. |
---|
24 | # Timezones. |
---|
25 | # Testing for daylight saving etc. |
---|
26 | # Suggestions? |
---|
27 | # Further Development? |
---|
28 | # Could be merged with 'myth_upcoming_recordings.pl'. |
---|
29 | # Functionality could be included in MythWeb to provide a live webCal interface. |
---|
30 | # Use Data::iCal (not in Fedora 8 yet). |
---|
31 | # Suggestions? |
---|
32 | # Revision History: |
---|
33 | # * 2008-05-16 0.1-1 David Greenhouse <daveg at dgnode dot screaming dot net> |
---|
34 | # - Initial creation. |
---|
35 | |
---|
36 | use strict; |
---|
37 | use warnings; |
---|
38 | use MythTV; |
---|
39 | |
---|
40 | #program version |
---|
41 | my $VERSION="0.1"; |
---|
42 | |
---|
43 | # Connect to the MythTV backend |
---|
44 | my $myth = new MythTV(); |
---|
45 | # Get a list of upcoming recordings |
---|
46 | my %rows = $myth->backend_rows('QUERY_GETALLPENDING', 2); |
---|
47 | my $row; |
---|
48 | my $show; |
---|
49 | # iCalendar wrapper |
---|
50 | format_value('BEGIN', 'VCALENDAR'); |
---|
51 | format_value('VERSION', '2.0'); |
---|
52 | format_value('PROGID', "-//MythTV Recording Schedule//NONSGML mythical $VERSION//EN"); |
---|
53 | # Content |
---|
54 | foreach $row (@{$rows{'rows'}}) { |
---|
55 | $show = new MythTV::Program(@$row); |
---|
56 | # Selection criteria... |
---|
57 | if (include_this($show)) { |
---|
58 | format_event($show); |
---|
59 | } |
---|
60 | } |
---|
61 | format_value('END', 'VCALENDAR'); |
---|
62 | |
---|
63 | # Test to include the given programme or not. |
---|
64 | # May need some more work, run-time options etc. |
---|
65 | sub include_this { |
---|
66 | my $prog = shift; |
---|
67 | return $prog->{'recstatus'} == $MythTV::recstatus_willrecord; |
---|
68 | } |
---|
69 | |
---|
70 | # Output an iCalendar entry for the given programme. |
---|
71 | sub format_event { |
---|
72 | my $prog = shift; |
---|
73 | format_value('BEGIN', 'VEVENT'); |
---|
74 | format_value('DTSTAMP', format_date(time())); |
---|
75 | format_value('UID', format_uid($prog)); |
---|
76 | format_value('DTSTART', format_date($prog->{'starttime'})); |
---|
77 | format_value('DTEND', format_date($prog->{'endtime'})); |
---|
78 | format_value('LAST-MODIFIED', format_date($prog->{'lastmodified'})); |
---|
79 | if ($prog->{'subtitle'} ne 'Untitled') { |
---|
80 | format_value('SUMMARY', $prog->{'title'} . ' - "' . $prog->{'subtitle'} . '"'); |
---|
81 | } else { |
---|
82 | format_value('SUMMARY', $prog->{'title'}); |
---|
83 | } |
---|
84 | format_value('DESCRIPTION', $prog->{'description'}); |
---|
85 | format_value('LOCATION', $prog->{'channel'}{'name'}); |
---|
86 | format_value('CATEGORIES', $prog->{'category'}); |
---|
87 | format_value('TRANSP', $prog->{'recstatus'} == $MythTV::recstatus_willrecord ? 'OPAQUE' : 'TRANSPARENT'); |
---|
88 | format_value('STATUS', $prog->{'recstatus'} == $MythTV::recstatus_willrecord ? 'CONFIRMED' : 'TENTATIVE'); |
---|
89 | format_value('PRIORITY', format_priority($prog)); |
---|
90 | format_value('X-MYTHTV-RECSTATUS', $MythTV::RecStatus_Types{$prog->{'recstatus'}}); |
---|
91 | format_value('END', 'VEVENT'); |
---|
92 | } |
---|
93 | |
---|
94 | # Format an entry line with the given tag and value. |
---|
95 | # Escapes and wraps, terminate with CR-NL. |
---|
96 | sub format_value { |
---|
97 | my $tag = shift; |
---|
98 | my $value = shift; |
---|
99 | # Escapes |
---|
100 | $value =~ s/;/\\;/g; |
---|
101 | $value =~ s/,/\\,/g; |
---|
102 | $value =~ s/"/\\"/g; |
---|
103 | # Wrap at col 76 |
---|
104 | my $text = $tag . ":" . $value; |
---|
105 | while (length($text) > 76) { |
---|
106 | print substr($text, 0, 76) . "\r\n"; |
---|
107 | $text = ' ' . substr($text, 76); |
---|
108 | } |
---|
109 | if ($text ne ' ') { |
---|
110 | print $text . "\r\n"; |
---|
111 | } |
---|
112 | } |
---|
113 | |
---|
114 | # Format a date/time value as an iCalendar Date/Time |
---|
115 | sub format_date { |
---|
116 | my $localtime = shift; |
---|
117 | my ($ts,$tm,$th,$dd,$dm,$dy) = localtime($localtime); |
---|
118 | # YYYYMMDDThhmmss |
---|
119 | return sprintf "%4.4d%2.2d%2.2dT%2.2d%2.2d%2.2d", $dy + 1900, $dm + 1, $dd, $th, $tm, $ts; |
---|
120 | } |
---|
121 | |
---|
122 | # Generate a unique identifier for the given programme. |
---|
123 | # chanid_starttime@hostname |
---|
124 | sub format_uid { |
---|
125 | my $prog = shift; |
---|
126 | my ($ts,$tm,$th,$dd,$dm,$dy) = localtime($prog->{'starttime'}); |
---|
127 | return sprintf "%4.4s_%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d@%s", $prog->{'chanid'}, $dy + 1900, $dm + 1, $dd, $th, $tm, $ts, $prog->{'hostname'}; |
---|
128 | } |
---|
129 | |
---|
130 | # Simple classification of recording priority in to three levels. |
---|
131 | sub format_priority { |
---|
132 | my $prog = shift; |
---|
133 | my $pri = $prog->{'recpriority'}; |
---|
134 | if ($pri < 0) { |
---|
135 | return 'LOW'; |
---|
136 | } elsif ($pri > 0) { |
---|
137 | return 'HIGH'; |
---|
138 | } |
---|
139 | return 'MEDIUM'; |
---|
140 | } |
---|