xref: /haiku/src/apps/mediaconverter/MediaConverterApp.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
1 // Copyright 1999, Be Incorporated. All Rights Reserved.
2 // Copyright 2000-2004, Jun Suzuki. All Rights Reserved.
3 // Copyright 2007, Stephan Aßmus. All Rights Reserved.
4 // This file may be used under the terms of the Be Sample Code License.
5 #include "MediaConverterApp.h"
6 
7 #include <inttypes.h>
8 #include <new>
9 #include <stdio.h>
10 #include <string.h>
11 
12 #include <Alert.h>
13 #include <Catalog.h>
14 #include <fs_attr.h>
15 #include <Locale.h>
16 #include <MediaFile.h>
17 #include <MediaTrack.h>
18 #include <NumberFormat.h>
19 #include <Mime.h>
20 #include <Path.h>
21 #include <String.h>
22 #include <StringFormat.h>
23 #include <View.h>
24 
25 #include "MediaConverterWindow.h"
26 #include "MediaEncoderWindow.h"
27 #include "MessageConstants.h"
28 
29 
30 #undef B_TRANSLATION_CONTEXT
31 #define B_TRANSLATION_CONTEXT "MediaConverter"
32 
33 
34 const char APP_SIGNATURE[] = "application/x-vnd.Haiku-MediaConverter";
35 
36 
37 MediaConverterApp::MediaConverterApp()
38 	:
39 	BApplication(APP_SIGNATURE),
40 	fWin(NULL),
41 	fConvertThreadID(-1),
42 	fConverting(false),
43 	fCancel(false)
44 {
45 	// TODO: implement settings for window pos
46 	fWin = new MediaConverterWindow(BRect(50, 50, 520, 555));
47 }
48 
49 
50 MediaConverterApp::~MediaConverterApp()
51 {
52 	if (fConvertThreadID >= 0) {
53 		fCancel = true;
54 		status_t exitValue;
55 		wait_for_thread(fConvertThreadID, &exitValue);
56 	}
57 }
58 
59 
60 // #pragma mark -
61 
62 
63 void
64 MediaConverterApp::MessageReceived(BMessage *msg)
65 {
66 	switch (msg->what) {
67 		case FILE_LIST_CHANGE_MESSAGE:
68 			if (fWin->Lock()) {
69 				bool enable = fWin->CountSourceFiles() > 0;
70 				fWin->SetEnabled(enable, enable);
71 				fWin->Unlock();
72 			}
73 			break;
74 
75 		case START_CONVERSION_MESSAGE:
76 			if (!fConverting)
77 				StartConverting();
78 			break;
79 
80 		case CANCEL_CONVERSION_MESSAGE:
81 			fCancel = true;
82 			break;
83 
84 		case CONVERSION_DONE_MESSAGE:
85 			fCancel = false;
86 			fConverting = false;
87 			DetachCurrentMessage();
88 			BMessenger(fWin).SendMessage(msg);
89 			break;
90 
91 		default:
92 			BApplication::MessageReceived(msg);
93 			break;
94 	}
95 }
96 
97 
98 void
99 MediaConverterApp::ReadyToRun()
100 {
101 	fWin->Show();
102 	fWin->PostMessage(INIT_FORMAT_MENUS);
103 }
104 
105 
106 void
107 MediaConverterApp::RefsReceived(BMessage* msg)
108 {
109 	entry_ref ref;
110 	int32 i = 0;
111 	BString errorFiles;
112 	int32 errors = 0;
113 
114 	// from Open dialog or drag & drop
115 
116 	while (msg->FindRef("refs", i++, &ref) == B_OK) {
117 
118 		uint32 flags = 0; // B_MEDIA_FILE_NO_READ_AHEAD
119 		BMediaFile* file = new(std::nothrow) BMediaFile(&ref, flags);
120 
121 		if (file == NULL || file->InitCheck() != B_OK) {
122 			errorFiles << ref.name << "\n";
123 			errors++;
124 			delete file;
125 			continue;
126 		}
127 		if (fWin->Lock()) {
128 			if (!fWin->AddSourceFile(file, ref))
129 				delete file;
130 			fWin->Unlock();
131 		}
132 	}
133 
134 	if (errors) {
135 		BString alertText;
136 		static BStringFormat format(B_TRANSLATE("{0, plural, "
137 			"one{The file was not recognized as a supported media file:} "
138 			"other{# files were not recognized as supported media files:}}"));
139 		format.Format(alertText, errors);
140 
141 		alertText << "\n" << errorFiles;
142 		BAlert* alert = new BAlert((errors > 1) ?
143 			B_TRANSLATE("Error loading files") :
144 			B_TRANSLATE("Error loading a file"),
145 			alertText.String(),	B_TRANSLATE("Continue"), NULL, NULL,
146 			B_WIDTH_AS_USUAL, B_STOP_ALERT);
147 		alert->Go();
148 	}
149 }
150 
151 
152 // #pragma mark -
153 
154 
155 bool
156 MediaConverterApp::IsConverting() const
157 {
158 	return fConverting;
159 }
160 
161 
162 void
163 MediaConverterApp::StartConverting()
164 {
165 	bool locked = fWin->Lock();
166 
167 	if (locked && (fWin->CountSourceFiles() > 0)) {
168 		fConvertThreadID = spawn_thread(MediaConverterApp::_RunConvertEntry,
169 			"converter thread", B_LOW_PRIORITY, (void *)this);
170 		if (fConvertThreadID >= 0) {
171 			fConverting = true;
172 			fCancel = false;
173 			resume_thread(fConvertThreadID);
174 		}
175 	}
176 
177 	if (locked) {
178 		fWin->Unlock();
179 	}
180 }
181 
182 
183 void
184 MediaConverterApp::SetStatusMessage(const char* message)
185 {
186 	if (fWin != NULL && fWin->Lock()) {
187 		fWin->SetStatusMessage(message);
188 		fWin->Unlock();
189 	}
190 }
191 
192 
193 // #pragma mark -
194 
195 BEntry
196 MediaConverterApp::_CreateOutputFile(BDirectory directory,
197 	entry_ref* ref, media_file_format* outputFormat)
198 {
199 	BString name(ref->name);
200 	// create output file name
201 	int32 extIndex = name.FindLast('.');
202 	if (extIndex != B_ERROR)
203 		name.Truncate(extIndex + 1);
204 	else
205 		name.Append(".");
206 
207 	name.Append(outputFormat->file_extension);
208 
209 	BEntry directoryEntry;
210 	directory.GetEntry(&directoryEntry);
211 	if (!directoryEntry.Exists()) {
212 		BAlert* alert = new BAlert(B_TRANSLATE("Error"),
213 			B_TRANSLATE("Selected directory not found. "
214 				"Defaulting to /boot/home"),
215 			B_TRANSLATE("OK"));
216 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
217 		alert->Go();
218 		directory.SetTo("/boot/home");
219 	}
220 
221 	BEntry inEntry(ref);
222 	BEntry outEntry;
223 
224 	if (inEntry.InitCheck() == B_OK) {
225 		// ensure that output name is unique
226 		int32 len = name.Length();
227 		int32 i = 1;
228 		while (directory.Contains(name.String())) {
229 			name.Truncate(len);
230 			name << " " << i;
231 			i++;
232 		}
233 		outEntry.SetTo(&directory, name.String());
234 	}
235 
236 	return outEntry;
237 }
238 
239 
240 int32
241 MediaConverterApp::_RunConvertEntry(void* castToMediaConverterApp)
242 {
243 	MediaConverterApp* app = (MediaConverterApp*)castToMediaConverterApp;
244 	app->_RunConvert();
245 	return 0;
246 }
247 
248 
249 void
250 MediaConverterApp::_RunConvert()
251 {
252 	bigtime_t start = 0;
253 	bigtime_t end = 0;
254 	int32 audioQuality = 75;
255 	int32 videoQuality = 75;
256 
257 	if (fWin->Lock()) {
258 		char *a;
259 		start = strtoimax(fWin->StartDuration(), &a, 0) * 1000;
260 		end = strtoimax(fWin->EndDuration(), &a, 0) * 1000;
261 		audioQuality = fWin->AudioQuality();
262 		videoQuality = fWin->VideoQuality();
263 		fWin->Unlock();
264 	}
265 
266 	int32 srcIndex = 0;
267 
268 	BMediaFile *inFile(NULL), *outFile(NULL);
269 	BEntry outEntry;
270 	entry_ref inRef;
271 	entry_ref outRef;
272 	BPath path;
273 	BString name;
274 
275 	while (!fCancel) {
276 		if (fWin->Lock()) {
277 			status_t r = fWin->GetSourceFileAt(srcIndex, &inFile, &inRef);
278 			if (r == B_OK) {
279 				media_codec_info* audioCodec;
280 				media_codec_info* videoCodec;
281 				media_file_format* fileFormat;
282 				fWin->GetSelectedFormatInfo(&fileFormat, &audioCodec,
283 					&videoCodec);
284 				BDirectory directory = fWin->OutputDirectory();
285 				fWin->Unlock();
286 				outEntry = _CreateOutputFile(directory, &inRef, fileFormat);
287 
288 				// display file name
289 
290 				outEntry.GetPath(&path);
291 				name.SetTo(path.Leaf());
292 
293 				if (outEntry.InitCheck() == B_OK) {
294 					entry_ref outRef;
295 					outEntry.GetRef(&outRef);
296 					outFile = new BMediaFile(&outRef, fileFormat);
297 
298 					BString tmp(
299 						B_TRANSLATE("Output file '%filename' created"));
300 					tmp.ReplaceAll("%filename", name);
301 					name = tmp;
302 				} else {
303 					BString tmp(B_TRANSLATE("Error creating '%filename'"));
304 					tmp.ReplaceAll("%filename", name);
305 					name = tmp;
306 				}
307 
308 				if (fWin->Lock()) {
309 					fWin->SetFileMessage(name.String());
310 					fWin->Unlock();
311 				}
312 
313 				if (outFile != NULL) {
314 					r = _ConvertFile(inFile, outFile, audioCodec, videoCodec,
315 						audioQuality, videoQuality, start, end);
316 
317 					// set mime
318 					update_mime_info(path.Path(), false, false, false);
319 
320 					fWin->Lock();
321 					if (r == B_OK) {
322 						fWin->RemoveSourceFile(srcIndex);
323 					} else {
324 						srcIndex++;
325 						BString error(
326 							B_TRANSLATE("Error converting '%filename'"));
327 						error.ReplaceAll("%filename", inRef.name);
328 						fWin->SetStatusMessage(error.String());
329 					}
330 					fWin->Unlock();
331 				}
332 			} else {
333 				srcIndex++;
334 				BString error(
335 					B_TRANSLATE("Error converting '%filename'"));
336 				error.ReplaceAll("%filename", inRef.name);
337 				fWin->SetStatusMessage(error.String());
338 				fWin->Unlock();
339 				break;
340 			}
341 		} else {
342 			break;
343 		}
344 	}
345 
346 	BMessenger(this).SendMessage(CONVERSION_DONE_MESSAGE);
347 }
348 
349 
350 // #pragma mark -
351 
352 
353 status_t
354 MediaConverterApp::_ConvertFile(BMediaFile* inFile, BMediaFile* outFile,
355 	media_codec_info* audioCodec, media_codec_info* videoCodec,
356 	int32 audioQuality, int32 videoQuality,
357 	bigtime_t startDuration, bigtime_t endDuration)
358 {
359 	BMediaTrack* inVidTrack = NULL;
360 	BMediaTrack* inAudTrack = NULL;
361 	BMediaTrack* outVidTrack = NULL;
362 	BMediaTrack* outAudTrack = NULL;
363 
364 	media_format inFormat;
365 	media_format outAudFormat;
366 	media_format outVidFormat;
367 
368 	media_raw_audio_format* raf = NULL;
369 	media_raw_video_format* rvf = NULL;
370 
371 	int32 width = -1;
372 	int32 height = -1;
373 
374 	uint8* videoBuffer = NULL;
375 	uint8* audioBuffer = NULL;
376 
377 	// gather the necessary format information and construct output tracks
378 	int64 videoFrameCount = 0;
379 	int64 audioFrameCount = 0;
380 
381 	status_t ret = B_OK;
382 	bool multiTrack = false;
383 
384 	BNumberFormat fNumberFormat;
385 
386 	int32 tracks = inFile->CountTracks();
387 	for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) {
388 		BMediaTrack* inTrack = inFile->TrackAt(i);
389 		inFormat.Clear();
390 		inTrack->EncodedFormat(&inFormat);
391 		if (inFormat.IsAudio() && (audioCodec != NULL)) {
392 			if (outAudTrack != NULL) {
393 				multiTrack = true;
394 				continue;
395 			}
396 			inAudTrack = inTrack;
397 			outAudFormat.Clear();
398 			outAudFormat.type = B_MEDIA_RAW_AUDIO;
399 			raf = &(outAudFormat.u.raw_audio);
400 			inTrack->DecodedFormat(&outAudFormat);
401 
402 			audioBuffer = new uint8[raf->buffer_size];
403 //			audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
404 //			audioFrameSize = (raf->format & 0xf) * raf->channel_count;
405 			outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec);
406 
407 			// Negociate the format with the inTrack again in case the codec
408 			// made some changes to it...
409 			inTrack->DecodedFormat(&outAudFormat);
410 
411 			if (outAudTrack != NULL) {
412 				if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK
413 					&& fWin->Lock()) {
414 					fWin->SetAudioQualityLabel(
415 						B_TRANSLATE("Audio quality not supported"));
416 					fWin->Unlock();
417 				}
418 			} else {
419 				SetStatusMessage(B_TRANSLATE("Error creating track."));
420 			}
421 
422 		} else if (inFormat.IsVideo() && (videoCodec != NULL)) {
423 			if (outVidTrack != NULL) {
424 				multiTrack = true;
425 				continue;
426 			}
427 			inVidTrack = inTrack;
428 			width = (int32)inFormat.Width();
429 			height = (int32)inFormat.Height();
430 
431 			// construct desired decoded video format
432 			outVidFormat.Clear();
433 			outVidFormat.type = B_MEDIA_RAW_VIDEO;
434 			rvf = &(outVidFormat.u.raw_video);
435 			rvf->last_active = (uint32)(height - 1);
436 			rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT;
437 			rvf->display.format = B_RGB32;
438 			rvf->display.bytes_per_row = 4 * width;
439 			rvf->display.line_width = width;
440 			rvf->display.line_count = height;
441 
442 			inVidTrack->DecodedFormat(&outVidFormat);
443 
444 			if (rvf->display.format == B_RGBA32) {
445 				printf("fixing color space (B_RGBA32 -> B_RGB32)");
446 				rvf->display.format = B_RGB32;
447 			}
448 			// Transfer the display aspect ratio.
449 			if (inFormat.type == B_MEDIA_ENCODED_VIDEO) {
450 				rvf->pixel_width_aspect
451 					= inFormat.u.encoded_video.output.pixel_width_aspect;
452 				rvf->pixel_height_aspect
453 					= inFormat.u.encoded_video.output.pixel_height_aspect;
454 			} else {
455 				rvf->pixel_width_aspect
456 					= inFormat.u.raw_video.pixel_width_aspect;
457 				rvf->pixel_height_aspect
458 					= inFormat.u.raw_video.pixel_height_aspect;
459 			}
460 
461 			videoBuffer = new (std::nothrow) uint8[height
462 				* rvf->display.bytes_per_row];
463 			outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec);
464 
465 			if (outVidTrack != NULL) {
466 				// DLM Added to use 3ivx Parameter View
467 				const char* videoQualitySupport = NULL;
468 				BView* encoderView = outVidTrack->GetParameterView();
469 				if (encoderView) {
470 					MediaEncoderWindow* encoderWin
471 						= new MediaEncoderWindow(BRect(50, 50, 520, 555),
472 							encoderView);
473 					encoderWin->Go();
474 						// blocks until the window is quit
475 
476 					// The quality setting is ignored by the 3ivx encoder if the
477 					// view was displayed, but this method is the trigger to
478 					// read all the parameter settings
479 					outVidTrack->SetQuality(videoQuality / 100.0f);
480 
481 					// We can now delete the encoderView created for us by the
482 					// encoder
483 					delete encoderView;
484 					encoderView = NULL;
485 
486 					videoQualitySupport
487 						= B_TRANSLATE("Video using parameters form settings");
488 				} else if (outVidTrack->SetQuality(videoQuality / 100.0f)
489 					>= B_OK) {
490 					videoQualitySupport
491 						= B_TRANSLATE("Video quality not supported");
492 				}
493 
494 				if (videoQualitySupport && fWin->Lock()) {
495 					fWin->SetVideoQualityLabel(videoQualitySupport);
496 					fWin->Unlock();
497 				}
498 			} else {
499 				SetStatusMessage(B_TRANSLATE("Error creating video."));
500 			}
501 		} else {
502 			//  didn't do anything with the track
503 			SetStatusMessage(
504 				B_TRANSLATE("Input file not recognized as Audio or Video"));
505 			inFile->ReleaseTrack(inTrack);
506 		}
507 	}
508 
509 	if (!outVidTrack && !outAudTrack) {
510 		printf("MediaConverterApp::_ConvertFile() - no tracks found!\n");
511 		ret = B_ERROR;
512 	}
513 
514 	if (multiTrack) {
515 		BAlert* alert = new BAlert(B_TRANSLATE("Multi-track file detected"),
516 		B_TRANSLATE("The file has multiple audio or video tracks, only the first one of each will "
517 			"be converted."),
518 		B_TRANSLATE("Understood"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
519 		alert->Go();
520 	}
521 
522 	if (fCancel) {
523 		// don't have any video or audio tracks here, or cancelled
524 		printf("MediaConverterApp::_ConvertFile()"
525 				" - user canceled before transcoding\n");
526 		ret = B_CANCELED;
527 	}
528 
529 	if (ret < B_OK) {
530 		delete[] audioBuffer;
531 		delete[] videoBuffer;
532 		delete outFile;
533 		return ret;
534 	}
535 
536 	outFile->CommitHeader();
537 	// this is where you would call outFile->AddCopyright(...)
538 
539 	int64 framesRead;
540 	media_header mh;
541 	int32 lastPercent, currPercent;
542 	float completePercent;
543 	BString status;
544 
545 	int64 start;
546 	int64 end;
547 	int32 stat = 0;
548 
549 	// read video from source and write to destination, if necessary
550 	if (outVidTrack != NULL) {
551 		lastPercent = -1;
552 		videoFrameCount = inVidTrack->CountFrames();
553 		if (endDuration == 0 || endDuration < startDuration) {
554 			start = 0;
555 			end = videoFrameCount;
556 		} else {
557 			inVidTrack->SeekToTime(&endDuration, stat);
558 			end = inVidTrack->CurrentFrame();
559 			inVidTrack->SeekToTime(&startDuration, stat);
560 			start = inVidTrack->CurrentFrame();
561 			if (end > videoFrameCount)
562 				end =  videoFrameCount;
563 			if (start > end)
564 				start = 0;
565 		}
566 
567 		framesRead = 0;
568 		for (int64 i = start; (i < end) && !fCancel; i += framesRead) {
569 			if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead,
570 					&mh)) != B_OK) {
571 				fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n", i, strerror(ret));
572 				status.SetToFormat(B_TRANSLATE("Error read video frame %" B_PRId64), i);
573 				SetStatusMessage(status.String());
574 
575 				break;
576 			}
577 
578 			if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead,
579 					mh.u.encoded_video.field_flags)) != B_OK) {
580 				fprintf(stderr, "Error writing video frame %" B_PRId64 ": %s\n", i, strerror(ret));
581 				status.SetToFormat(B_TRANSLATE("Error writing video frame %" B_PRId64), i);
582 				SetStatusMessage(status.String());
583 
584 				break;
585 			}
586 			completePercent = (float)(i - start) / (float)(end - start) * 100;
587 			currPercent = (int32)completePercent;
588 			if (currPercent > lastPercent) {
589 				lastPercent = currPercent;
590 				BString data;
591 				double percentValue = (double)currPercent;
592 
593 				if (fNumberFormat.FormatPercent(data, percentValue / 100) != B_OK) {
594 					data.SetToFormat("%" B_PRId32 "%%", currPercent);
595 				}
596 
597 				status.SetToFormat(B_TRANSLATE("Writing video track: %s complete"), data.String());
598 				SetStatusMessage(status.String());
599 			}
600 		}
601 		outVidTrack->Flush();
602 		inFile->ReleaseTrack(inVidTrack);
603 	}
604 
605 	// read audio from source and write to destination, if necessary
606 	if (outAudTrack != NULL) {
607 		lastPercent = -1;
608 
609 		audioFrameCount =  inAudTrack->CountFrames();
610 
611 		if (endDuration == 0 || endDuration < startDuration) {
612 			start = 0;
613 			end = audioFrameCount;
614 		} else {
615 			inAudTrack->SeekToTime(&endDuration, stat);
616 			end = inAudTrack->CurrentFrame();
617 			inAudTrack->SeekToTime(&startDuration, stat);
618 			start = inAudTrack->CurrentFrame();
619 			if (end > audioFrameCount)
620 				end = audioFrameCount;
621 			if (start > end)
622 				start = 0;
623 		}
624 
625 		for (int64 i = start; (i < end) && !fCancel; i += framesRead) {
626 			if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead,
627 				&mh)) != B_OK) {
628 				fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret));
629 				status.SetToFormat(B_TRANSLATE("Error read audio frame %" B_PRId64), i);
630 				SetStatusMessage(status.String());
631 
632 				break;
633 			}
634 
635 			if ((ret = outAudTrack->WriteFrames(audioBuffer,
636 				framesRead)) != B_OK) {
637 				fprintf(stderr, "Error writing audio frames: %s\n",	strerror(ret));
638 				status.SetToFormat(B_TRANSLATE("Error writing audio frame %" B_PRId64), i);
639 				SetStatusMessage(status.String());
640 
641 				break;
642 			}
643 			completePercent = (float)(i - start) / (float)(end - start) * 100;
644 			currPercent = (int32)completePercent;
645 			if (currPercent > lastPercent) {
646 				lastPercent = currPercent;
647 				BString data;
648 				double percentValue = (double)currPercent;
649 
650 				if (fNumberFormat.FormatPercent(data, percentValue / 100) != B_OK) {
651 					data.SetToFormat("%" B_PRId32 "%%", currPercent);
652 				}
653 
654 				status.SetToFormat(B_TRANSLATE("Writing audio track: %s complete"), data.String());
655 				SetStatusMessage(status.String());
656 			}
657 		}
658 		outAudTrack->Flush();
659 		inFile->ReleaseTrack(inAudTrack);
660 
661 	}
662 
663 	outFile->CloseFile();
664 	delete outFile;
665 
666 	delete[] videoBuffer;
667 	delete[] audioBuffer;
668 
669 	return ret;
670 }
671 
672 
673 // #pragma mark -
674 
675 
676 int
677 main(int, char **)
678 {
679 	MediaConverterApp app;
680 	app.Run();
681 
682 	return 0;
683 }
684