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