xref: /haiku/src/bin/translate.cpp (revision 850f2d1e58cc443f77353c7fc0ce0c158c1fd328)
1 /*
2  * Copyright 2003-2007, Haiku. All Rights Reserved.
3  * Distributed under the terms of the MIT license.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  *		jonas.sundstrom@kirilla.com
8  */
9 
10 
11 #include <TranslationKit.h>
12 #include <Application.h>
13 #include <String.h>
14 #include <File.h>
15 #include <Path.h>
16 #include <Entry.h>
17 #include <Mime.h>
18 #include <NodeInfo.h>
19 
20 #include <ctype.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <strings.h>
24 
25 
26 extern const char *__progname;
27 const char *gProgramName = __progname;
28 bool gVerbose = false;
29 
30 
31 class TypeList {
32 	public:
33 		void Add(uint32 type);
34 		bool Remove(uint32 type);
35 		bool FindType(uint32 type);
36 
37 		void SetTo(TypeList &types);
38 		int32 Count();
39 		uint32 TypeAt(int32 index);
40 
41 	private:
42 		BList	fList;
43 };
44 
45 class RemovingFile : public BFile {
46 	public:
47 		RemovingFile(const char *path);
48 		~RemovingFile();
49 
50 		void Keep();
51 
52 	private:
53 		BEntry	fEntry;
54 };
55 
56 class Translator {
57 	public:
58 		Translator(const char *inPath, const char *outPath, uint32 outFormat);
59 		~Translator();
60 
61 		status_t Translate();
62 
63 	private:
64 		status_t Directly(BFile &input, translator_info &info, BFile &output);
65 		status_t Indirectly(BFile &input, BFile &output);
66 		status_t FindPath(const translation_format *info, BPositionIO &stream,
67 					TypeList &typesSeen, TypeList &path, double &pathQuality);
68 		status_t GetMimeTypeFromCode(uint32 type, char *mimeType);
69 
70 	private:
71 		BTranslatorRoster *fRoster;
72 		BPath	fInputPath, fOutputPath;
73 		uint32	fOutputFormat;
74 };
75 
76 class TranslateApp : public BApplication {
77 	public:
78 		TranslateApp(void);
79 		virtual ~TranslateApp();
80 
81 		virtual void ReadyToRun(void);
82 		virtual void ArgvReceived(int32 argc, char **argv);
83 
84 	private:
85 		void PrintUsage(void);
86 		void ListTranslators(uint32 type);
87 
88 		uint32 GetTypeCodeForOutputMime(const char *mime);
89 		uint32 GetTypeCodeFromString(const char *string);
90 		status_t Translate(BFile &input, translator_info &translator, BFile &output, uint32 type);
91 		status_t Translate(BFile &input, BFile &output, uint32 type);
92 		status_t Translate(const char *inPath, const char *outPath, uint32 type);
93 
94 		bool				fGotArguments;
95 		BTranslatorRoster	*fTranslatorRoster;
96 		int32				fTranslatorCount;
97 		translator_id		*fTranslatorArray;
98 };
99 
100 
101 static void
102 print_tupel(const char *format, uint32 value)
103 {
104 	char tupel[5];
105 
106 	for (int32 i = 0; i < 4; i++) {
107 		tupel[i] = (value >> (24 - i * 8)) & 0xff;
108 		if (!isprint(tupel[i]))
109 			tupel[i] = '.';
110 	}
111 	tupel[4] = '\0';
112 
113 	printf(format, tupel);
114 }
115 
116 
117 static void
118 print_translation_format(const translation_format &format)
119 {
120 	print_tupel("'%s' ", format.type);
121 	print_tupel("(%s) ", format.group);
122 
123 	printf("%.1f %.1f %s ; %s\n", format.quality, format.capability,
124 		format.MIME, format.name);
125 }
126 
127 
128 //	#pragma mark -
129 
130 
131 void
132 TypeList::Add(uint32 type)
133 {
134 	fList.AddItem((void *)(addr_t)type, 0);
135 }
136 
137 
138 bool
139 TypeList::Remove(uint32 type)
140 {
141 	return fList.RemoveItem((void *)(addr_t)type);
142 }
143 
144 
145 bool
146 TypeList::FindType(uint32 type)
147 {
148 	return fList.IndexOf((void *)(addr_t)type) >= 0;
149 }
150 
151 
152 void
153 TypeList::SetTo(TypeList &types)
154 {
155 	fList.MakeEmpty();
156 
157 	for (int32 i = 0; i < types.Count(); i++)
158 		fList.AddItem((void *)(addr_t)types.TypeAt(i));
159 }
160 
161 
162 int32
163 TypeList::Count()
164 {
165 	return fList.CountItems();
166 }
167 
168 
169 uint32
170 TypeList::TypeAt(int32 index)
171 {
172 	return (uint32)(addr_t)fList.ItemAt(index);
173 }
174 
175 
176 //	#pragma mark -
177 
178 
179 RemovingFile::RemovingFile(const char *path)
180 	: BFile(path, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE),
181 	fEntry(path)
182 {
183 }
184 
185 
186 RemovingFile::~RemovingFile()
187 {
188 	fEntry.Remove();
189 }
190 
191 
192 void
193 RemovingFile::Keep()
194 {
195 	fEntry.Unset();
196 }
197 
198 
199 //	#pragma mark -
200 
201 
202 Translator::Translator(const char *inPath, const char *outPath, uint32 outputFormat)
203 	:
204 	fRoster(BTranslatorRoster::Default()),
205 	fInputPath(inPath),
206 	fOutputPath(outPath),
207 	fOutputFormat(outputFormat)
208 {
209 }
210 
211 
212 Translator::~Translator()
213 {
214 }
215 
216 
217 status_t
218 Translator::Translate()
219 {
220 	// input file
221 	BFile input;
222 	status_t status = input.SetTo(fInputPath.Path(), B_READ_ONLY);
223 	if (status != B_OK) {
224 		fprintf(stderr, "%s: could not open \"%s\": %s\n",
225 			gProgramName, fInputPath.Path(), strerror(status));
226 		return status;
227 	}
228 
229 	// find a direct translator
230 	bool direct = true;
231 	translator_info translator;
232 	status = fRoster->Identify(&input, NULL, &translator, 0, NULL, fOutputFormat);
233 	if (status < B_OK) {
234 		// no direct translator found - let's try with something else
235 		status = fRoster->Identify(&input, NULL, &translator);
236 		if (status < B_OK) {
237 			fprintf(stderr, "%s: identifying \"%s\" failed: %s\n",
238 				gProgramName, fInputPath.Path(), strerror(status));
239 			return status;
240 		}
241 
242 		direct = false;
243 	}
244 
245 	// output file
246 	RemovingFile output(fOutputPath.Path());
247 	if ((status = output.InitCheck()) != B_OK) {
248 		fprintf(stderr, "%s: Could not create \"%s\": %s\n",
249 			gProgramName, fOutputPath.Path(), strerror(status));
250 		return status;
251 	}
252 
253 	if (direct)
254 		status = Directly(input, translator, output);
255 	else
256 		status = Indirectly(input, output);
257 
258 	if (status == B_OK) {
259 		output.Keep();
260 
261 		// add filetype attribute
262 		update_mime_info(fOutputPath.Path(), false, true, false);
263 
264 		char mimeType[B_ATTR_NAME_LENGTH];
265 		BNode node(fOutputPath.Path());
266 		BNodeInfo info(&node);
267 		if (info.GetType(mimeType) != B_OK || !strcasecmp(mimeType, B_FILE_MIME_TYPE)) {
268 			// the Registrar couldn't find a type for this file
269 			// so let's use the information we have from the
270 			// translator
271 			if (GetMimeTypeFromCode(fOutputFormat, mimeType) == B_OK)
272 				info.SetType(mimeType);
273 		}
274 	} else {
275 		fprintf(stderr, "%s: translating failed: %s\n",
276 			gProgramName, strerror(status));
277 	}
278 
279 	return status;
280 }
281 
282 
283 /** Converts the input file to the output file using the
284  *	specified translator.
285  */
286 
287 status_t
288 Translator::Directly(BFile &input, translator_info &info, BFile &output)
289 {
290 	if (gVerbose)
291 		printf("Direct translation from \"%s\"\n", info.name);
292 
293 	return fRoster->Translate(&input, &info, NULL, &output, fOutputFormat);
294 }
295 
296 
297 /**	Converts the input file to the output file by computing the best
298  *	quality path between the input type and the output type, and then
299  *	applies as many translators as needed.
300  */
301 
302 status_t
303 Translator::Indirectly(BFile &input, BFile &output)
304 {
305 	TypeList translatorPath;
306 	TypeList handledTypes;
307 	double quality;
308 
309 	if (FindPath(NULL, input, handledTypes, translatorPath, quality) != B_OK)
310 		return B_NO_TRANSLATOR;
311 
312 	// The initial input stream is the input file which gets translated into
313 	// the first buffer. After that, the two buffers fill each other, until
314 	// the end is reached and the output file is the translation target
315 
316 	BMallocIO buffer[2];
317 	BPositionIO *inputStream = &input;
318 	BPositionIO *outputStream = &buffer[0];
319 
320 	for (int32 i = 0; i < translatorPath.Count(); i++) {
321 		uint32 type = translatorPath.TypeAt(i);
322 		if (type == fOutputFormat)
323 			outputStream = &output;
324 		else
325 			outputStream->SetSize(0);
326 
327 		inputStream->Seek(0, SEEK_SET);
328 			// rewind the input stream
329 
330 		status_t status = fRoster->Translate(inputStream, NULL, NULL, outputStream, type);
331 		if (status != B_OK)
332 			return status;
333 
334 		// switch buffers
335 		inputStream = &buffer[i % 2];
336 		outputStream = &buffer[(i + 1) % 2];
337 
338 		if (gVerbose)
339 			print_tupel("  '%s'\n", type);
340 	}
341 
342 	return B_OK;
343 }
344 
345 
346 status_t
347 Translator::FindPath(const translation_format *format, BPositionIO &stream,
348 	TypeList &typesSeen, TypeList &path, double &pathQuality)
349 {
350 	translator_info *infos = NULL;
351 	translator_id *ids = NULL;
352 	int32 count;
353 	uint32 inFormat = 0;
354 	uint32 group = 0;
355 	status_t status;
356 
357 	// Get a list of capable translators (or all of them)
358 
359 	if (format == NULL) {
360 		status = fRoster->GetTranslators(&stream, NULL, &infos, &count);
361 		if (status == B_OK && count > 0) {
362 			inFormat = infos[0].type;
363 			group = infos[0].group;
364 
365 			if (gVerbose) {
366 				puts("Indirect translation:");
367 				print_tupel("  '%s', ", inFormat);
368 				printf("%s\n", infos[0].name);
369 			}
370 		}
371 	} else {
372 		status = fRoster->GetAllTranslators(&ids, &count);
373 		inFormat = format->type;
374 		group = format->group;
375 	}
376 	if (status != B_OK || count == 0) {
377 		delete[] infos;
378 		delete[] ids;
379 		return status;
380 	}
381 
382 	// build the best path to get from here to fOutputFormat recursively
383 	// (via depth search, best quality/capability wins)
384 
385 	TypeList bestPath;
386 	double bestQuality = -1;
387 	status = B_NO_TRANSLATOR;
388 
389 	for (int32 i = 0; i < count; i++) {
390 		const translation_format *formats;
391 		int32 formatCount;
392 		bool matches = false;
393 		int32 id = ids ? ids[i] : infos[i].translator;
394 		if (fRoster->GetInputFormats(id, &formats, &formatCount) != B_OK)
395 			continue;
396 
397 		// see if this translator is good enough for us
398 		for (int32 j = 0; j < formatCount; j++) {
399 			if (formats[j].type == inFormat) {
400 				matches = true;
401 				break;
402 			}
403 		}
404 
405 		if (!matches)
406 			continue;
407 
408 		// search output formats
409 
410 		if (fRoster->GetOutputFormats(id, &formats, &formatCount) != B_OK)
411 			continue;
412 
413 		typesSeen.Add(inFormat);
414 
415 		for (int32 j = 0; j < formatCount; j++) {
416 			if (formats[j].group != group || typesSeen.FindType(formats[j].type))
417 				continue;
418 
419 			double formatQuality = formats[j].quality * formats[j].capability;
420 
421 			if (formats[j].type == fOutputFormat) {
422 				// we've found our target type, so we can stop the path here
423 				bestPath.Add(formats[j].type);
424 				bestQuality = formatQuality;
425 				status = B_OK;
426 				continue;
427 			}
428 
429 			TypeList path;
430 			double quality;
431 			if (FindPath(&formats[j], stream, typesSeen, path, quality) == B_OK) {
432 				if (bestQuality < quality * formatQuality) {
433 					bestQuality = quality * formatQuality;
434 					bestPath.SetTo(path);
435 					bestPath.Add(formats[j].type);
436 					status = B_OK;
437 				}
438 			}
439 		}
440 
441 		typesSeen.Remove(inFormat);
442 	}
443 
444 	if (status == B_OK) {
445 		pathQuality = bestQuality;
446 		path.SetTo(bestPath);
447 	}
448 	delete[] infos;
449 	delete[] ids;
450 	return status;
451 }
452 
453 
454 status_t
455 Translator::GetMimeTypeFromCode(uint32 type, char *mimeType)
456 {
457 	translator_id *ids = NULL;
458 	int32 count;
459 	status_t status = fRoster->GetAllTranslators(&ids, &count);
460 	if (status != B_OK)
461 		return status;
462 
463 	status = B_NO_TRANSLATOR;
464 
465 	for (int32 i = 0; i < count; i++) {
466 		const translation_format *format = NULL;
467 		int32 formatCount = 0;
468 		fRoster->GetOutputFormats(ids[i], &format, &formatCount);
469 
470 		for (int32 j = 0; j < formatCount; j++) {
471 			if (type == format[j].type) {
472 				strcpy(mimeType, format[j].MIME);
473 				status = B_OK;
474 				break;
475 			}
476 		}
477 	}
478 
479 	delete[] ids;
480 	return status;
481 }
482 
483 
484 //	#pragma mark -
485 
486 
487 TranslateApp::TranslateApp(void)
488 	: BApplication("application/x-vnd.haiku-translate"),
489 	fGotArguments(false),
490 	fTranslatorRoster(BTranslatorRoster::Default()),
491 	fTranslatorCount(0),
492 	fTranslatorArray(NULL)
493 {
494 	fTranslatorRoster->GetAllTranslators(&fTranslatorArray, &fTranslatorCount);
495 }
496 
497 
498 TranslateApp::~TranslateApp()
499 {
500 	delete[] fTranslatorArray;
501 }
502 
503 
504 void
505 TranslateApp::ArgvReceived(int32 argc, char **argv)
506 {
507 	if (argc < 2
508 		|| !strcmp(argv[1], "--help"))
509 		return;
510 
511 	if (!strcmp(argv[1], "--list")) {
512 		fGotArguments = true;
513 
514 		uint32 type = B_TRANSLATOR_ANY_TYPE;
515 		if (argc > 2)
516 			type = GetTypeCodeFromString(argv[2]);
517 
518 		ListTranslators(type);
519 		return;
520 	}
521 
522 	if (!strcmp(argv[1], "--verbose")) {
523 		fGotArguments = true;
524 		argc--;
525 		argv++;
526 		gVerbose = true;
527 	}
528 
529 	if (argc != 4)
530 		return;
531 
532 	fGotArguments = true;
533 
534 	// get typecode of output format
535 	uint32 outputFormat = 0;
536 	BMimeType mime(argv[3]);
537 
538 	if (mime.IsValid() && !mime.IsSupertypeOnly()) {
539 		// MIME-string
540 		outputFormat = GetTypeCodeForOutputMime(argv[3]);
541 	} else
542 		outputFormat = GetTypeCodeFromString(argv[3]);
543 
544 	if (outputFormat == 0) {
545 		fprintf(stderr, "%s: bad format: %s\nformat is 4-byte type code or full MIME type\n",
546 			gProgramName, argv[3]);
547 		exit(-1);
548 	}
549 
550 	Translator translator(argv[1], argv[2], outputFormat);
551 	status_t status = translator.Translate();
552 	if (status < B_OK)
553 		exit(-1);
554 }
555 
556 
557 void
558 TranslateApp::ReadyToRun(void)
559 {
560 	if (fGotArguments == false)
561 		PrintUsage();
562 
563 	PostMessage(B_QUIT_REQUESTED);
564 }
565 
566 
567 void
568 TranslateApp::PrintUsage(void)
569 {
570 	printf("usage: %s { --list [type] | input output format }\n"
571 		"\t\"format\" can expressed as 4-byte type code (ie. 'TEXT') or as MIME type.\n",
572 		gProgramName);
573 }
574 
575 
576 void
577 TranslateApp::ListTranslators(uint32 type)
578 {
579 	for (int32 i = 0; i < fTranslatorCount; i++) {
580 		const char *name = NULL;
581 		const char *info = NULL;
582 		int32 version = 0;
583 		if (fTranslatorRoster->GetTranslatorInfo(fTranslatorArray[i], &name, &info, &version) != B_OK)
584 			continue;
585 
586 		const translation_format *inputFormats = NULL;
587 		const translation_format *outputFormats = NULL;
588 		int32 inCount = 0, outCount = 0;
589 		fTranslatorRoster->GetInputFormats(fTranslatorArray[i], &inputFormats, &inCount);
590 		fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &outputFormats, &outCount);
591 
592 		// test if the translator has formats of the specified type
593 
594 		if (type != B_TRANSLATOR_ANY_TYPE) {
595 			bool matches = false;
596 
597 			for (int32 j = 0; j < inCount; j++) {
598 				if (inputFormats[j].group == type || inputFormats[j].type == type) {
599 					matches = true;
600 					break;
601 				}
602 			}
603 
604 			for (int32 j = 0; j < outCount; j++) {
605 				if (outputFormats[j].group == type || outputFormats[j].type == type) {
606 					matches = true;
607 					break;
608 				}
609 			}
610 
611 			if (!matches)
612 				continue;
613 		}
614 
615 		printf("name: %s\ninfo: %s\nversion: %" B_PRId32 ".%" B_PRId32 ".%"
616 			B_PRId32 "\n", name, info,
617 			B_TRANSLATION_MAJOR_VERSION(version),
618 			B_TRANSLATION_MINOR_VERSION(version),
619 			B_TRANSLATION_REVISION_VERSION(version));
620 
621 		for (int32 j = 0; j < inCount; j++) {
622 			printf("  input:\t");
623 			print_translation_format(inputFormats[j]);
624 		}
625 
626 		for (int32 j = 0; j < outCount; j++) {
627 			printf("  output:\t");
628 			print_translation_format(outputFormats[j]);
629 		}
630 
631 		printf("\n");
632 	}
633 }
634 
635 
636 uint32
637 TranslateApp::GetTypeCodeForOutputMime(const char *mime)
638 {
639 	for (int32 i = 0; i < fTranslatorCount; i++) {
640 		const translation_format *format = NULL;
641 		int32 count = 0;
642 		fTranslatorRoster->GetOutputFormats(fTranslatorArray[i], &format, &count);
643 
644 		for (int32 j = 0; j < count; j++) {
645 			if (!strcmp(mime, format[j].MIME))
646 				return format[j].type;
647 		}
648 	}
649 
650 	return 0;
651 }
652 
653 
654 uint32
655 TranslateApp::GetTypeCodeFromString(const char *string)
656 {
657 	size_t length = strlen(string);
658 	if (length > 4)
659 		return 0;
660 
661 	uint8 code[4] = {' ', ' ', ' ', ' '};
662 
663 	for (uint32 i = 0; i < length; i++)
664 		code[i] = (uint8)string[i];
665 
666 	return B_HOST_TO_BENDIAN_INT32(*(uint32 *)code);
667 }
668 
669 
670 //	#pragma mark -
671 
672 
673 int
674 main()
675 {
676 	new TranslateApp();
677 	be_app->Run();
678 
679 	return B_OK;
680 }
681 
682