1###__PERLBIN__###
2#  Copyright (C) 2002-2007 Adrian Ulrich <pab at blinkenlights.ch>
3#  Part of the gnupod-tools collection
4#
5#  URL: http://www.gnu.org/software/gnupod/
6#
7#    GNUpod is free software; you can redistribute it and/or modify
8#    it under the terms of the GNU General Public License as published by
9#    the Free Software Foundation; either version 3 of the License, or
10#    (at your option) any later version.
11#
12#    GNUpod is distributed in the hope that it will be useful,
13#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#    GNU General Public License for more details.
16#
17#    You should have received a copy of the GNU General Public License
18#    along with this program.  If not, see <http://www.gnu.org/licenses/>.#
19#
20# iTunes and iPod are trademarks of Apple
21#
22# This product is not supported/written/published by Apple!
23
24use strict;
25use GNUpod::XMLhelper;
26use GNUpod::FooBar;
27use GNUpod::ArtworkDB;
28use Getopt::Long;
29
30use vars qw(%opts @keeplist %rename_tags);
31
32use constant DEFAULT_SPACE => 32;
33
34my $dbid     = undef;  # Artwork DB-ID
35my $dirty    = 0;      # Do we need to re-write the XML version?
36
37$opts{mount} = $ENV{IPOD_MOUNTPOINT};
38
39
40
41print "gnupod_search.pl Version ###__VERSION__### (C) Adrian Ulrich\n";
42
43# WARNING: If you add new options wich don't do matching, change newfile()
44#
45GetOptions(\%opts, "version", "help|h", "mount|m=s", "artist|a=s",
46                   "album|l=s", "title|t=s", "id|i=s", "rename=s@", "artwork=s",
47                   "playcount|c=s", "rating|s=s", "podcastrss|R=s", "podcastguid|U=s",
48                   "bitrate|b=s",
49                   "view=s","genre|g=s", "match-once|o", "delete");
50GNUpod::FooBar::GetConfig(\%opts, {view=>'s', mount=>'s', 'match-once'=>'b', 'automktunes'=>'b', model=>'s'}, "gnupod_search");
51
52$opts{view} ||= 'ialt'; #Default view
53
54usage()   if $opts{help};
55version() if $opts{version};
56#Check if input makes sense:
57die "You can't use --delete and --rename together\n" if($opts{delete} && $opts{rename});
58
59# -> Connect the iPod
60my $connection = GNUpod::FooBar::connect(\%opts);
61usage($connection->{status}."\n") if $connection->{status};
62
63my $AWDB  = GNUpod::ArtworkDB->new(Connection=>$connection, DropUnseen=>1);
64
65main($connection);
66
67####################################################
68# Worker
69sub main {
70	my($con) = @_;
71
72	#Build %rename_tags
73	foreach(@{$opts{rename}}) {
74		my($key,$val) =  split(/=/,$_,2);
75		next unless $key && defined($val);
76		#$key =~ s/^\s*-+//g; # -- is not valid for xml tags!
77		next if lc($key) eq "id";#Dont allow something like THIS
78		$rename_tags{lc($key)} = $val;
79	}
80
81	if($opts{artwork}) {
82		if( $AWDB->PrepareImage(File=>$opts{artwork}, Model=>$opts{model}) ) {
83			$AWDB->LoadArtworkDb or die "Failed to load artwork database\n";
84		}
85		else {
86			warn "$0: Could not load $opts{artwork}, skipping artwork\n";
87			delete($opts{artwork});
88		}
89	}
90
91	pview(undef,1);
92	GNUpod::XMLhelper::doxml($con->{xml}) or usage("Failed to parse $con->{xml}, did you run gnupod_INIT.pl?\n");
93	#XML::Parser finished, write new file if we deleted or renamed
94	if($dirty) {
95		GNUpod::XMLhelper::writexml($con,{automktunes=>$opts{automktunes}});
96	}
97
98	$AWDB->WriteArtworkDb;
99}
100
101#############################################
102# Eventhandler for FILE items
103sub newfile {
104	my($el) =  @_;
105                          # 2 = mount + view (both are ALWAYS set)
106	my $ntm      = keys(%opts)-2-$opts{'match-once'}-$opts{automktunes}-$opts{delete}-(defined $opts{rename})-(defined $opts{artwork})-(defined $opts{model});
107	my $matched  = undef;
108	my $dounlink = 0;
109	foreach my $opx (keys(%opts)) {
110		next if $opx =~ /mount|match-once|delete|view|rename/; #Skip this
111
112
113		if(substr($opts{$opx},0,1) eq ">") {
114			$matched++ if  int($el->{file}->{$opx}) > int(substr($opts{$opx},1));
115		}
116		elsif(substr($opts{$opx},0,1) eq "<") {
117			$matched++ if  int($el->{file}->{$opx}) < int(substr($opts{$opx},1));
118		}
119		elsif(substr($opts{$opx},0,1) eq "-") {
120			my($s_from, $s_to) = substr($opts{$opx},1) =~ /^(\d+)-(\d+)$/;
121			if( (int($el->{file}->{$opx}) >= $s_from) && (int($el->{file}->{$opx}) <= $s_to) ) {
122				$matched++;
123			}
124		}
125		elsif($el->{file}->{$opx} =~ /$opts{$opx}/i) {
126			$matched++;
127		}
128	}
129
130
131	if(($opts{'match-once'} && $matched) || $ntm == $matched) {
132		# => HIT
133
134		# -> Rename tags
135		foreach(keys(%rename_tags)) {
136			$el->{file}->{$_} = $rename_tags{$_};
137			$dirty++;
138		}
139		# -> Print output
140		pview($el->{file},undef,$opts{delete});
141
142		if($opts{delete}) {
143			$dounlink = 1; # Request deletion
144		}
145		elsif(defined($opts{artwork})) {
146			# -> Add/Set artwork
147			$el->{file}->{has_artwork} = 1;
148			$el->{file}->{artworkcnt}  = 1;
149			$el->{file}->{dbid_1}      = $AWDB->InjectImage;
150			$dirty++;
151		}
152	}
153
154	if($dounlink) {
155		# -> Remove file as requested
156		unlink(GNUpod::XMLhelper::realpath($opts{mount},$el->{file}->{path})) or warn "[!!] Remove failed: $!\n";
157		$dirty++;
158	}
159	else {
160		# -> Keep file: add it to XML
161		GNUpod::XMLhelper::mkfile($el);
162		# -> and keep artwork
163		$AWDB->KeepImage($el->{file}->{dbid_1});
164		# -> and playlists
165		$keeplist[$el->{file}->{id}] = 1;
166	}
167}
168
169############################################
170# Eventhandler for PLAYLIST items
171sub newpl {
172	# Delete or rename needs to rebuild the XML file
173	my ($el, $name, $plt) = @_;
174	if(($plt eq "pl" or $plt eq "pcpl") && ref($el->{add}) eq "HASH") { #Add action
175		if(defined($el->{add}->{id}) && int(keys(%{$el->{add}})) == 1) { #Only id
176			return unless($keeplist[$el->{add}->{id}]); #ID not on keeplist. drop it
177		}
178	}
179	elsif($plt eq "spl" && ref($el->{splcont}) eq "HASH") { #spl content
180		if(defined($el->{splcont}->{id}) && int(keys(%{$el->{splcont}})) == 1) { #Only one item
181			return unless($keeplist[$el->{splcont}->{id}]);
182		}
183	}
184	GNUpod::XMLhelper::mkfile($el,{$plt."name"=>$name});
185}
186
187
188##############################################################
189# Printout Search output
190sub pview {
191 my($orf,$xhead, $xdelete) = @_;
192
193 #Build refs
194 my %qh = ();
195 $qh{n}{k} = $orf->{songnum};   $qh{n}{w} = 4;  $qh{n}{n} = "SNUM";
196 $qh{t}{k} = $orf->{title};                     $qh{t}{s} = "TITLE";
197 $qh{a}{k} = $orf->{artist};                    $qh{a}{s} = "ARTIST";
198 $qh{r}{k} = $orf->{rating};    $qh{r}{w} = 4;  $qh{r}{s} = "RTNG";
199 $qh{p}{k} = $orf->{path};      $qh{p}{w} = 96; $qh{p}{s} = "PATH";
200 $qh{l}{k} = $orf->{album};                     $qh{l}{s} = "ALBUM";
201 $qh{g}{k} = $orf->{genre};                     $qh{g}{s} = "GENRE";
202 $qh{R}{k} = $orf->{podcastrss};                $qh{R}{s} = "RSS";
203 $qh{G}{k} = $orf->{podcastguid};               $qh{G}{s} = "GUID";
204 $qh{c}{k} = $orf->{playcount}; $qh{c}{w} = 4;  $qh{c}{s} = "CNT";
205 $qh{i}{k} = $orf->{id};        $qh{i}{w} = 4;  $qh{i}{s} = "ID";
206 $qh{d}{k} = $orf->{dbid_1};    $qh{d}{w} = 16; $qh{d}{s} = "DBID";
207 $qh{b}{k} = $orf->{bitrate};   $qh{b}{w} = 8;  $qh{b}{s} = "BITRATE";
208 $qh{u}{k} = GNUpod::XMLhelper::realpath($opts{mount},$orf->{path}); $qh{u}{w} = 96; $qh{u}{s} = "UNIXPATH";
209
210 #Prepare view
211
212 my $ll = 0; #LineLength
213  foreach(split(//,$opts{view})) {
214      print "|" if $ll;
215      my $cs = $qh{$_}{k};           #CurrentString
216         $cs = $qh{$_}{s} if $xhead; #Replace it if HEAD is needed
217
218      my $cl = $qh{$_}{w}||DEFAULT_SPACE;       #Current length
219         $ll += $cl+1;               #Incrase LineLength
220     printf("%-*s",$cl,$cs);
221  }
222
223  if($xdelete && !$xhead) {
224   print " [RM]\n";
225  }
226  elsif($xhead) {
227   print "\n";
228   print "=" x $ll;
229   print "\n";
230  }
231  else {
232   print "\n";
233  }
234
235}
236
237
238###############################################################
239# Basic help
240sub usage {
241my($rtxt) = @_;
242die << "EOF";
243$rtxt
244Usage: gnupod_search.pl [-h] [-m directory] File1 File2 ...
245
246   -h, --help              display this help and exit
247       --version           output version information and exit
248   -m, --mount=directory   iPod mountpoint, default is \$IPOD_MOUNTPOINT
249   -t, --title=TITLE       search songs by Title
250   -a, --artist=ARTIST     search songs by Artist
251   -l, --album=ALBUM       search songs by Album
252   -i, --id=ID             search songs by ID
253   -g, --genre=GENRE       search songs by Genre
254   -c, --playcount=COUNT   search songs by Playcount
255   -s, --rating=COUNT      search songs by Rating (20 is one star, 40 two, etc.)
256   -R, --podcastrss=RSS    search songs by RSS
257   -U, --podcastguid=GUID  search songs by GUID
258   -b, --bitrate=BITRATE   search songs by Bitrate
259   -o, --match-once        Search doesn't need to match multiple times (eg. -a & -l)
260       --delete            REMOVE (!) matched songs
261       --view=ialt         Modify output, default=ialt
262                            t = title    a = artist   r = rating      p = iPod Path
263                            l = album    g = genre    c = playcount   i = id
264                            u = UnixPath n = Songnum  G = podcastguid R = podcastrss
265                            d = dbid
266       --rename=KEY=VAL    Change tags on found songs. Example: --rename="ARTIST=Foo Bar"
267       --artwork=FILE      Set FILE as Cover for found files, do not forget to run mktunes.pl
268
269Note: * Argument for title/artist/album/etc has to be UTF8 encoded, *not* latin1!
270      * Use '>3' to search all values above 3, use '<3' to search for values below 3
271      * Use '-10-30' to search all values between (and including) 10 to 30.
272      * Everything else is handled as regular expressions! If you want to search for
273        eg. ID '3' (excluding 13,63,32..), you would have to write: --id="^3\$"
274
275Report bugs to <bug-gnupod\@nongnu.org>
276EOF
277}
278
279
280sub version {
281die << "EOF";
282gnupod_search.pl (gnupod) ###__VERSION__###
283Copyright (C) Adrian Ulrich 2002-2008
284
285This is free software; see the source for copying conditions.  There is NO
286warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
287
288EOF
289}
290
291