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