1 /*  mode_len.c - len mode module
2  *  Copyright (C) 2000-2009  Jason Jordan <shnutils@freeshell.org>
3  *
4  *  This program is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU General Public License
6  *  as published by the Free Software Foundation; either version 2
7  *  of the License, or (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18 
19 #include <string.h>
20 #include "mode.h"
21 
22 CVSID("$Id: mode_len.c,v 1.90 2009/03/17 17:23:05 jason Exp $")
23 
24 static bool len_main(int,char **);
25 static void len_help(void);
26 
27 mode_module mode_len = {
28   "len",
29   "shnlen",
30   "Displays length, size and properties of PCM WAVE data",
31   CVSIDSTR,
32   FALSE,
33   len_main,
34   len_help
35 };
36 
37 #define LEN_OK             "-"
38 #define LEN_NOT_APPLICABLE "x"
39 
40 typedef enum {
41   LEVEL_UNKNOWN = -1,
42   LEVEL_BYTES,
43   LEVEL_KBYTES,
44   LEVEL_MBYTES,
45   LEVEL_GBYTES,
46   LEVEL_TBYTES
47 } len_levels;
48 
49 static int totals_unit_level = LEVEL_UNKNOWN;
50 static int file_unit_level = LEVEL_UNKNOWN;
51 static int num_processed = 0;
52 static bool all_cd_quality = TRUE;
53 static bool suppress_column_names = FALSE;
54 static bool suppress_totals_line = FALSE;
55 
56 static double total_size = 0.0;
57 static double total_data_size = 0.0;
58 static double total_disk_size = 0.0;
59 static double total_length = 0.0;
60 static double unit_divs[5] = {1.0, 1024.0, 1048576.0, 1073741824.0, 1099511627776.0};
61 
62 static char *units[5] = {"B ", "KB", "MB", "GB", "TB"};
63 
64 static void len_help()
65 {
66   st_info("Usage: %s [OPTIONS] [files]\n",st_progname());
67   st_info("\n");
68   st_info("Mode-specific options:\n");
69   st_info("\n");
70   st_info("  -U unit show total size in specified unit (*)\n");
71   st_info("  -c      suppress column names\n");
72   st_info("  -h      show this help screen\n");
73   st_info("  -t      suppress totals line\n");
74   st_info("  -u unit show file sizes in specified unit (*)\n");
75   st_info("\n");
76   st_info("          (*) unit is one of: {[b], kb, mb, gb, tb}\n");
77   st_info("\n");
78 }
79 
80 static int get_unit(char *unit)
81 {
82   if (!strcmp(optarg,"b"))
83     return LEVEL_BYTES;
84 
85   if (!strcmp(optarg,"kb"))
86     return LEVEL_KBYTES;
87 
88   if (!strcmp(optarg,"mb"))
89     return LEVEL_MBYTES;
90 
91   if (!strcmp(optarg,"gb"))
92     return LEVEL_GBYTES;
93 
94   if (!strcmp(optarg,"tb"))
95     return LEVEL_TBYTES;
96 
97   return LEVEL_UNKNOWN;
98 }
99 
100 static void parse(int argc,char **argv,int *first_arg)
101 {
102   int c;
103 
104   file_unit_level = LEVEL_BYTES;
105   totals_unit_level = LEVEL_BYTES;
106 
107   while ((c = st_getopt(argc,argv,"U:ctu:")) != -1) {
108     switch (c) {
109       case 'U':
110         if (NULL == optarg)
111           st_error("missing total size unit");
112         totals_unit_level = get_unit(optarg);
113         if (LEVEL_UNKNOWN == totals_unit_level)
114           st_help("unknown total size unit: [%s]",optarg);
115         break;
116       case 'c':
117         suppress_column_names = TRUE;
118         break;
119       case 't':
120         suppress_totals_line = TRUE;
121         break;
122       case 'u':
123         if (NULL == optarg)
124           st_error("missing file size unit");
125         file_unit_level = get_unit(optarg);
126         if (LEVEL_UNKNOWN == file_unit_level)
127           st_help("unknown file size unit: [%s]",optarg);
128         break;
129     }
130   }
131 
132   *first_arg = optind;
133 }
134 
135 static void show_len_banner()
136 {
137   if (suppress_column_names)
138     return;
139 
140   st_output("    length     expanded size    cdr  WAVE problems  fmt   ratio  filename\n");
141 }
142 
143 static void print_formatted_length(wave_info *info)
144 {
145   int i,len;
146 
147   len = strlen(info->m_ss);
148 
149   if (PROB_NOT_CD(info)) {
150     for (i=0;i<13-len;i++)
151       st_output(" ");
152     st_output("%s",info->m_ss);
153   }
154   else {
155     for (i=0;i<12-len;i++)
156       st_output(" ");
157     st_output("%s ",info->m_ss);
158   }
159 }
160 
161 static bool show_stats(wave_info *info)
162 {
163   wlong appended_bytes;
164   bool success;
165 
166   success = FALSE;
167 
168   print_formatted_length(info);
169 
170   if (file_unit_level > 0)
171     st_output("%14.2f",(double)info->total_size / unit_divs[file_unit_level]);
172   else
173     st_output("%14lu",info->total_size);
174 
175   st_output(" %s",units[file_unit_level]);
176 
177   /* CD-R properties */
178 
179   st_output("  ");
180 
181   if (PROB_NOT_CD(info))
182     st_output("c%s%s",LEN_NOT_APPLICABLE,LEN_NOT_APPLICABLE);
183   else {
184     st_output("%s",LEN_OK);
185     if (PROB_BAD_BOUND(info))
186       st_output("b");
187     else
188       st_output("%s",LEN_OK);
189 
190     if (PROB_TOO_SHORT(info))
191       st_output("s");
192     else
193       st_output("%s",LEN_OK);
194   }
195 
196   /* WAVE properties */
197 
198   st_output("   ");
199 
200   if (PROB_HDR_NOT_CANONICAL(info))
201     st_output("h");
202   else
203     st_output("%s",LEN_OK);
204 
205   if (PROB_EXTRA_CHUNKS(info))
206     st_output("e");
207   else
208     st_output("%s",LEN_OK);
209 
210   /* problems */
211 
212   st_output("   ");
213 
214   if (info->file_has_id3v2_tag)
215     st_output("3");
216   else
217     st_output("%s",LEN_OK);
218 
219   if (PROB_DATA_NOT_ALIGNED(info))
220     st_output("a");
221   else
222     st_output("%s",LEN_OK);
223 
224   if (PROB_HDR_INCONSISTENT(info))
225     st_output("i");
226   else
227     st_output("%s",LEN_OK);
228 
229   if (!info->input_format->is_compressed && !info->input_format->is_translated) {
230     appended_bytes = info->actual_size - info->total_size - info->id3v2_tag_size;
231 
232     if (PROB_TRUNCATED(info))
233       st_output("t");
234     else
235       st_output("%s",LEN_OK);
236 
237     if (PROB_JUNK_APPENDED(info) && appended_bytes > 0)
238       st_output("j");
239     else
240       st_output("%s",LEN_OK);
241   }
242   else
243     st_output("%s%s",LEN_NOT_APPLICABLE,LEN_NOT_APPLICABLE);
244 
245   /* input file format */
246   st_output("  %5s",info->input_format->name);
247 
248   /* ratio */
249   st_output("  %0.4f",(double)info->actual_size/(double)info->total_size);
250 
251   st_output("  %s\n",info->filename);
252 
253   success = TRUE;
254 
255   return success;
256 }
257 
258 static void show_totals_line()
259 {
260   wave_info *info;
261   wlong seconds;
262   double ratio;
263 
264   if (suppress_totals_line)
265     return;
266 
267   if (NULL == (info = new_wave_info(NULL)))
268     st_error("could not allocate memory for totals");
269 
270   if (all_cd_quality) {
271     /* Note to users from the year 2376:  the m:ss.ff value on the totals line will only be
272      * correct as long as the total data size is less than 689 terabytes (2^32 * 176400 bytes).
273      * Hopefully, by then you'll all have 2048-bit processers to go with your petabyte keychain
274      * raid arrays, and this won't be an issue.
275      */
276 
277     /* calculate floor of total length in seconds */
278     seconds = (wlong)(total_data_size / (double)CD_RATE);
279 
280     /* since length_to_str() only considers (data_size % CD_RATE) when the file is CD-quality,
281      * we don't need to risk overflowing a 32-bit unsigned long with a double - we can cheat
282      * and just store the modulus.
283      */
284     info->data_size = (wlong)(total_data_size - (double)(seconds * CD_RATE));
285 
286     info->length = seconds;
287     info->rate = CD_RATE;
288   }
289   else {
290     info->problems |= PROBLEM_NOT_CD_QUALITY;
291     info->length = (wlong)total_length;
292   }
293 
294   info->exact_length = total_length;
295 
296   length_to_str(info);
297 
298   print_formatted_length(info);
299 
300   ratio = (num_processed > 0) ? (total_disk_size / total_size) : 0.0;
301 
302   total_size /= unit_divs[totals_unit_level];
303 
304   if (totals_unit_level > 0)
305     st_output("%14.2f",total_size);
306   else
307     st_output("%14.0f",total_size);
308 
309   st_output(" %s                           %0.4f  (%d file%s)\n",
310     units[totals_unit_level],ratio,num_processed,(1 != num_processed)?"s":"");
311 
312   st_free(info);
313 }
314 
315 static void update_totals(wave_info *info)
316 {
317   total_size += (double)info->total_size;
318   total_data_size += (double)info->data_size;
319   total_length += (double)info->data_size / (double)info->avg_bytes_per_sec;
320 
321   if (PROB_NOT_CD(info))
322     all_cd_quality = FALSE;
323 
324   total_disk_size += (double)info->actual_size;
325 
326   num_processed++;
327 }
328 
329 static bool process_file(char *filename)
330 {
331   wave_info *info;
332   bool success;
333 
334   if (NULL == (info = new_wave_info(filename)))
335     return FALSE;
336 
337   if ((success = show_stats(info)))
338     update_totals(info);
339 
340   st_free(info);
341 
342   return success;
343 }
344 
345 static bool process(int argc,char **argv,int start)
346 {
347   char *filename;
348   bool success;
349 
350   success = TRUE;
351 
352   show_len_banner();
353 
354   input_init(start,argc,argv);
355 
356   while ((filename = input_get_filename())) {
357     success = (process_file(filename) && success);
358   }
359 
360   show_totals_line();
361 
362   return success;
363 }
364 
365 static bool len_main(int argc,char **argv)
366 {
367   int first_arg;
368 
369   parse(argc,argv,&first_arg);
370 
371   return process(argc,argv,first_arg);
372 }
373