1#!/usr/bin/env perl
2
3use Net::UPnP::ControlPoint;
4use Net::UPnP::AV::MediaServer;
5
6use Shell qw(curl ffmpeg);
7
8#curl('--version');
9#ffmpeg('-version');
10
11#------------------------------
12# program info
13#------------------------------
14
15$program_name = 'DLNA Media Sever 2 Vodcast';
16$copy_right = 'Copyright (c) 2005 Satoshi Konno';
17$script_name = 'dms2vodcast.pl';
18$script_version = '1.0.3';
19
20#------------------------------
21# global variables
22#------------------------------
23
24@dms_content_list = ();
25
26#------------------------------
27# command option
28#------------------------------
29
30$rss_file_name = "";
31$base_directory = "./";
32$rss_base_url= "http://localhost";
33$rss_description = "CyberGarage Vodcast";
34$rss_language = "";
35$rss_link= "";
36$rss_title = "CyberGarage";
37$requested_count = 0;
38$mp4_format = 'ipod';
39$title_regexp = "";
40
41@command_opt = (
42['-b', '--base-url', '<url>', 'Set the base url in the item link property of the output RSS file'],
43['-B', '--base-directory', '<url>', 'Set the base directory to output the RSS file and the MPEG4 files'],
44['-d', '--rss-description', '<description>', 'Set the description tag in the output RSS file'],
45['-g', '--rss-language', '<language>', 'Set the language tag in the output RSS file'],
46['-h', '--help', '', 'This is help text.'],
47['-l', '--rss-link', '<link>', 'Set the link tag in the output RSS file'],
48['-r', '--requested-count', '<url>', 'Set the max request count to the media server contents'],
49['-t', '--rss-title', '<file>', 'Set the title tag in the output RSS file'],
50['-f', '--mp4-format', '<ipod | psp>', 'Set the MPEG4 format'],
51['-s', '--search-title', '<regular expression>', 'Set the regular expression of the content titles by UTF-8'],
52);
53
54sub is_command_option {
55	($opt) = @_;
56	for ($n=0; $n<@command_opt; $n++) {
57		if ($opt eq $command_opt[$n][0] || $opt eq $command_opt[$n][1]) {
58			return $n;
59		}
60	}
61	return -1;
62}
63
64#------------------------------
65# main (pase command line)
66#------------------------------
67
68for ($i=0; $i<(@ARGV); $i++) {
69	$opt = $ARGV[$i];
70	$opt_num = is_command_option($opt);
71	$opt_short_name = '';
72	if ($opt_num < 0) {
73		if ($opt =~ m/^-/) {
74			print "$script_name : option $opt is unknown\n";
75			print "$script_name : try \'$script_name --help\' for more information	\n";
76			exit 1;
77		}
78	}
79	else {
80			$opt_short_name = $command_opt[$opt_num][0];
81	}
82	if ($opt_short_name eq '-h') {
83		print "Usage : $script_name [options...] <output RSS file name>\n";
84		print "Options : \n";
85		$max_opt_output_len = 0;
86		for ($n=0; $n<@command_opt; $n++) {
87			$opt_output_len = length("$command_opt[$n][0]\/$command_opt[$n][1] $command_opt[$n][2]");
88			if ($max_opt_output_len <= $opt_output_len) {
89				$max_opt_output_len = $opt_output_len;
90			}
91		}
92		for ($n=0; $n<@command_opt; $n++) {
93			$opt_output_str = "$command_opt[$n][0]\/$command_opt[$n][1] $command_opt[$n][2]";
94			print $opt_output_str;
95			for ($j=0; $j<($max_opt_output_len-length($opt_output_str)); $j++) {
96				print " ";
97			}
98			print " $command_opt[$n][3]\n";
99		}
100		exit 1;
101	} elsif ($opt_short_name eq '-b') {
102		$rss_base_url = $ARGV[++$i];
103	} elsif ($opt_short_name eq '-B') {
104		$base_directory = $ARGV[++$i];
105	} elsif ($opt_short_name eq '-d') {
106		$rss_description = $ARGV[++$i];
107	} elsif ($opt_short_name eq '-g') {
108		$rss_language = $ARGV[++$i];
109	} elsif ($opt_short_name eq '-l') {
110		$rss_link = $ARGV[++$i];
111	} elsif ($opt_short_name eq '-r') {
112		$requested_count = $ARGV[++$i];
113	} elsif ($opt_short_name eq '-t') {
114		$rss_title = $ARGV[++$i];
115	} elsif ($opt_short_name eq '-f') {
116		$mp4_format = $ARGV[++$i];
117		if ($mp4_format ne 'ipod' && $mp4_format ne 'psp') {
118			print "Unkown MPEG4 format : $mp4_format !!\n";
119			exit 1;
120		}
121	} elsif ($opt_short_name eq '-s') {
122		$title_regexp = $ARGV[++$i];
123	} else {
124		$rss_file_name = $opt;
125	}
126}
127
128if (length($rss_file_name) <= 0) {
129	print "$script_name : Must specify a output RSS file name\n";
130	print "$script_name : try \'$script_name --help\' for more information	\n";
131	exit 1	;
132}
133
134print "$program_name (v$script_version), $copy_right\n";
135print "Output RSS file name = $rss_file_name\n";
136print "  title : $rss_title\n";
137print "  description : $rss_description\n";
138print "  language : $rss_language\n";
139print "  base url : $rss_base_url\n";
140print "  base directory : $base_directory\n";
141print "  requested_count : $requested_count\n";
142print "  mp4_format : $mp4_format\n";
143print "  search regexp : $title_regexp\n";
144
145#------------------------------
146# main
147#------------------------------
148
149my $obj = Net::UPnP::ControlPoint->new();
150
151$retry_cnt = 0;
152@dev_list = ();
153while (@dev_list <= 0 || $retry_cnt > 5) {
154#	@dev_list = $obj->search(st =>'urn:schemas-upnp-org:device:MediaServer:1', mx => 10);
155	@dev_list = $obj->search(st =>'upnp:rootdevice', mx => 3);
156	$retry_cnt++;
157}
158
159$devNum= 0;
160foreach $dev (@dev_list) {
161	$device_type = $dev->getdevicetype();
162	if  ($device_type ne 'urn:schemas-upnp-org:device:MediaServer:1') {
163		next;
164	}
165	unless ($dev->getservicebyname('urn:schemas-upnp-org:service:ContentDirectory:1')) {
166		next;
167	}
168	print "[$devNum] : " . $dev->getfriendlyname() . "\n";
169	$mediaServer = Net::UPnP::AV::MediaServer->new();
170	$mediaServer->setdevice($dev);
171	#@content_list = $mediaServer->getcontentlist(ObjectID => 0, RequestedCount => $requested_count);
172	@content_list = $mediaServer->getcontentlist(ObjectID => 0);
173	#print "content_list = @content_list\n";
174	foreach $content (@content_list) {
175		parse_content_directory($mediaServer, $content);
176	}
177	$devNum++;
178}
179
180#------------------------------
181# Output RSS file
182#------------------------------
183
184if (@dms_content_list <= 0) {
185	print "Couldn't find video contents !!\n";
186	exit 1;
187}
188
189$output_rss_filename = $base_directory . $rss_file_name;
190
191open(RSS_FILE, ">$output_rss_filename") || die "Couldn't open the specifed output file($output_rss_filename)\n";
192
193$rss_header = <<"RSS_HEADER";
194<?xml version="1.0" encoding="utf-8"?>
195<rss xmlns:itunes="http://www.itunes.com/DTDs/Podcast-1.0.dtd" version="2.0">
196<channel>
197<title>$rss_title</title>
198<language>$rss_language</language>
199<description>$rss_description</description>
200<link>$rss_link</link>
201RSS_HEADER
202print RSS_FILE $rss_header;
203
204foreach $content (@dms_content_list){
205	$title = $content->{'title'};
206	$fname = $content->{'file_name'};
207	$fsize = $content->{'file_size'};
208
209$mp4_link = $rss_base_url . $fname;
210$mp4_item = <<"RSS_MP4_ITEM";
211<item>
212<title>$title</title>
213<guid isPermalink="false">$mp4_link</guid>
214<enclosure url="$mp4_link" length="$fsize" type="video/mp4" />
215</item>
216RSS_MP4_ITEM
217	print RSS_FILE $mp4_item;
218}
219
220$rss_footer = <<"RSS_FOOTER";
221</channel>
222</rss>
223RSS_FOOTER
224print RSS_FILE $rss_footer;
225
226	close(RSS_FILE);
227
228$rss_outputed_items = @dms_content_list;
229print "Outputed $rss_outputed_items RSS items to $output_rss_filename\n";
230
231#------------------------------
232# parse_content_directory
233#------------------------------
234
235sub parse_content_directory {
236	($mediaServer, $content) = @_;
237	my $objid = $content->getid();
238
239	if ($content->isitem()) {
240		my $title = $content->gettitle();
241		my $mime = $content->getcontenttype();
242		if ( ($mime =~ m/video/) && ( (length($title_regexp) == 0) || ($title =~ m/$title_regexp/) ) ) {
243			my $dms_content_count = @dms_content_list;
244			if ($requested_count == 0 || $dms_content_count < $requested_count) {
245				my $mp4_content = mpeg2tompeg4($mediaServer, $content);
246				if (defined($mp4_content)) {
247					push(@dms_content_list, $mp4_content);
248				}
249			}
250		}
251	}
252
253	unless ($content->iscontainer()) {
254		return;
255	}
256
257	my @child_content_list = $mediaServer->getcontentlist(ObjectID => $objid );
258
259	if (@child_content_list <= 0) {
260		return;
261	}
262
263	foreach my $child_content (@child_content_list) {
264		parse_content_directory($mediaServer, $child_content);
265	}
266}
267
268#------------------------------
269# mpeg2tompeg4
270#------------------------------
271
272sub mpeg2tompeg4 {
273	($mediaServer, $content) = @_;
274	my $objid = $content->getid();
275	my $title = $content->gettitle();
276	my $url = $content->geturl();
277
278	print "[$objid] $title ($url)\n";
279
280	my $dev = $mediaServer->getdevice();
281	my $dev_friendlyname = $dev->getfriendlyname();
282	my $dev_udn = $dev->getudn();
283	$dev_udn =~ s/:/-/g;
284
285	my $filename_body = $dev_friendlyname . "_" . $dev_udn . "_" . $objid;
286	$filename_body =~ s/ //g;
287	$filename_body =~ s/\//-/g;
288
289	my $mpeg2_file_name = $filename_body . ".mpeg";
290	my $mpeg4_file_name = $filename_body . "_" . $mp4_format . ".m4v";
291	my $output_mpeg4_file_name = $base_directory . $mpeg4_file_name;
292
293	if (!(-e $output_mpeg4_file_name)) {
294		$curl_opt = "\"$url\" -o \"$mpeg2_file_name\"";
295		print "curl $curl_opt\n";
296		curl($curl_opt);
297
298		if ($mp4_format eq 'psp') {
299			$ffmpeg_opt = "-y -i \"$mpeg2_file_name\" -bitexact -fixaspect -s 320x240 -r 29.97 -b 768 -ar 24000 -ab 32 -f psp \"$output_mpeg4_file_name\"";
300		}
301		else {
302			$ffmpeg_opt = "-y -i \"$mpeg2_file_name\" -bitexact -fixaspect -s 320x240 -r 29.97 -b 850 -acodec aac -ac 2 -ar 44100 -ab 64 -f mp4 \"$output_mpeg4_file_name\"";
303		}
304
305		print "ffmpeg $ffmpeg_opt\n";
306		ffmpeg($ffmpeg_opt);
307
308		unlink($mpeg2_file_name);
309	}
310
311	if (!(-e $output_mpeg4_file_name)) {
312		return undef;
313	}
314
315	my $mpeg4_file_size = -s $output_mpeg4_file_name;
316
317	if ($mpeg4_file_size <= 0) {
318		return undef;
319	}
320
321	my %info = (
322		'objid' => $objid,
323		'title' => $title,
324		'file_name' => $mpeg4_file_name,
325		'file_size' => $mpeg4_file_size,
326	);
327
328	return \%info;
329}
330
331exit 0;
332
333