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