xref: /haiku/src/apps/mediaconverter/MediaConverterApp.cpp (revision 52c4471a3024d2eb81fe88e2c3982b9f8daa5e56)
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 	bool multiTrack = false;
382 
383 	int32 tracks = inFile->CountTracks();
384 	for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) {
385 		BMediaTrack* inTrack = inFile->TrackAt(i);
386 		inFormat.Clear();
387 		inTrack->EncodedFormat(&inFormat);
388 		if (inFormat.IsAudio() && (audioCodec != NULL)) {
389 			if (outAudTrack != NULL) {
390 				multiTrack = true;
391 				continue;
392 			}
393 			inAudTrack = inTrack;
394 			outAudFormat.Clear();
395 			outAudFormat.type = B_MEDIA_RAW_AUDIO;
396 			raf = &(outAudFormat.u.raw_audio);
397 			inTrack->DecodedFormat(&outAudFormat);
398 
399 			audioBuffer = new uint8[raf->buffer_size];
400 //			audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
401 //			audioFrameSize = (raf->format & 0xf) * raf->channel_count;
402 			outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec);
403 
404 			// Negociate the format with the inTrack again in case the codec
405 			// made some changes to it...
406 			inTrack->DecodedFormat(&outAudFormat);
407 
408 			if (outAudTrack != NULL) {
409 				if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK
410 					&& fWin->Lock()) {
411 					fWin->SetAudioQualityLabel(
412 						B_TRANSLATE("Audio quality not supported"));
413 					fWin->Unlock();
414 				}
415 			} else {
416 				SetStatusMessage(B_TRANSLATE("Error creating track."));
417 			}
418 
419 		} else if (inFormat.IsVideo() && (videoCodec != NULL)) {
420 			if (outVidTrack != NULL) {
421 				multiTrack = true;
422 				continue;
423 			}
424 			inVidTrack = inTrack;
425 			width = (int32)inFormat.Width();
426 			height = (int32)inFormat.Height();
427 
428 			// construct desired decoded video format
429 			outVidFormat.Clear();
430 			outVidFormat.type = B_MEDIA_RAW_VIDEO;
431 			rvf = &(outVidFormat.u.raw_video);
432 			rvf->last_active = (uint32)(height - 1);
433 			rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT;
434 			rvf->display.format = B_RGB32;
435 			rvf->display.bytes_per_row = 4 * width;
436 			rvf->display.line_width = width;
437 			rvf->display.line_count = height;
438 
439 			inVidTrack->DecodedFormat(&outVidFormat);
440 
441 			if (rvf->display.format == B_RGBA32) {
442 				printf("fixing color space (B_RGBA32 -> B_RGB32)");
443 				rvf->display.format = B_RGB32;
444 			}
445 			// Transfer the display aspect ratio.
446 			if (inFormat.type == B_MEDIA_ENCODED_VIDEO) {
447 				rvf->pixel_width_aspect
448 					= inFormat.u.encoded_video.output.pixel_width_aspect;
449 				rvf->pixel_height_aspect
450 					= inFormat.u.encoded_video.output.pixel_height_aspect;
451 			} else {
452 				rvf->pixel_width_aspect
453 					= inFormat.u.raw_video.pixel_width_aspect;
454 				rvf->pixel_height_aspect
455 					= inFormat.u.raw_video.pixel_height_aspect;
456 			}
457 
458 			videoBuffer = new (std::nothrow) uint8[height
459 				* rvf->display.bytes_per_row];
460 			outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec);
461 
462 			if (outVidTrack != NULL) {
463 				// DLM Added to use 3ivx Parameter View
464 				const char* videoQualitySupport = NULL;
465 				BView* encoderView = outVidTrack->GetParameterView();
466 				if (encoderView) {
467 					MediaEncoderWindow* encoderWin
468 						= new MediaEncoderWindow(BRect(50, 50, 520, 555),
469 							encoderView);
470 					encoderWin->Go();
471 						// blocks until the window is quit
472 
473 					// The quality setting is ignored by the 3ivx encoder if the
474 					// view was displayed, but this method is the trigger to
475 					// read all the parameter settings
476 					outVidTrack->SetQuality(videoQuality / 100.0f);
477 
478 					// We can now delete the encoderView created for us by the
479 					// encoder
480 					delete encoderView;
481 					encoderView = NULL;
482 
483 					videoQualitySupport
484 						= B_TRANSLATE("Video using parameters form settings");
485 				} else if (outVidTrack->SetQuality(videoQuality / 100.0f)
486 					>= B_OK) {
487 					videoQualitySupport
488 						= B_TRANSLATE("Video quality not supported");
489 				}
490 
491 				if (videoQualitySupport && fWin->Lock()) {
492 					fWin->SetVideoQualityLabel(videoQualitySupport);
493 					fWin->Unlock();
494 				}
495 			} else {
496 				SetStatusMessage(B_TRANSLATE("Error creating video."));
497 			}
498 		} else {
499 			//  didn't do anything with the track
500 			SetStatusMessage(
501 				B_TRANSLATE("Input file not recognized as Audio or Video"));
502 			inFile->ReleaseTrack(inTrack);
503 		}
504 	}
505 
506 	if (!outVidTrack && !outAudTrack) {
507 		printf("MediaConverterApp::_ConvertFile() - no tracks found!\n");
508 		ret = B_ERROR;
509 	}
510 
511 	if (multiTrack) {
512 		BAlert* alert = new BAlert(B_TRANSLATE("Multi-track file detected"),
513 		B_TRANSLATE("The file has multiple audio or video tracks, only the first one of each will "
514 			"be converted."),
515 		B_TRANSLATE("Understood"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
516 		alert->Go();
517 	}
518 
519 	if (fCancel) {
520 		// don't have any video or audio tracks here, or cancelled
521 		printf("MediaConverterApp::_ConvertFile()"
522 				" - user canceled before transcoding\n");
523 		ret = B_CANCELED;
524 	}
525 
526 	if (ret < B_OK) {
527 		delete[] audioBuffer;
528 		delete[] videoBuffer;
529 		delete outFile;
530 		return ret;
531 	}
532 
533 	outFile->CommitHeader();
534 	// this is where you would call outFile->AddCopyright(...)
535 
536 	int64 framesRead;
537 	media_header mh;
538 	int32 lastPercent, currPercent;
539 	float completePercent;
540 	BString status;
541 
542 	int64 start;
543 	int64 end;
544 	int32 stat = 0;
545 
546 	// read video from source and write to destination, if necessary
547 	if (outVidTrack != NULL) {
548 		lastPercent = -1;
549 		videoFrameCount = inVidTrack->CountFrames();
550 		if (endDuration == 0 || endDuration < startDuration) {
551 			start = 0;
552 			end = videoFrameCount;
553 		} else {
554 			inVidTrack->SeekToTime(&endDuration, stat);
555 			end = inVidTrack->CurrentFrame();
556 			inVidTrack->SeekToTime(&startDuration, stat);
557 			start = inVidTrack->CurrentFrame();
558 			if (end > videoFrameCount)
559 				end =  videoFrameCount;
560 			if (start > end)
561 				start = 0;
562 		}
563 
564 		framesRead = 0;
565 		for (int64 i = start; (i < end) && !fCancel; i += framesRead) {
566 			if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead,
567 					&mh)) != B_OK) {
568 				fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n",
569 					i, strerror(ret));
570 				snprintf(status.LockBuffer(128), 128,
571 						B_TRANSLATE("Error read video frame %" B_PRId64), i);
572 				status.UnlockBuffer();
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",
581 					i, strerror(ret));
582 				snprintf(status.LockBuffer(128), 128,
583 						B_TRANSLATE("Error writing video frame %" B_PRId64), i);
584 				status.UnlockBuffer();
585 				SetStatusMessage(status.String());
586 
587 				break;
588 			}
589 			completePercent = (float)(i - start) / (float)(end - start) * 100;
590 			currPercent = (int32)completePercent;
591 			if (currPercent > lastPercent) {
592 				lastPercent = currPercent;
593 				snprintf(status.LockBuffer(128), 128,
594 					B_TRANSLATE("Writing video track: %" B_PRId32 "%% complete"),
595 					currPercent);
596 				status.UnlockBuffer();
597 				SetStatusMessage(status.String());
598 
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 				snprintf(status.LockBuffer(128), 128,
630 					B_TRANSLATE("Error read audio frame %" B_PRId64), i);
631 				status.UnlockBuffer();
632 				SetStatusMessage(status.String());
633 
634 				break;
635 			}
636 
637 			if ((ret = outAudTrack->WriteFrames(audioBuffer,
638 				framesRead)) != B_OK) {
639 				fprintf(stderr, "Error writing audio frames: %s\n",	strerror(ret));
640 				snprintf(status.LockBuffer(128), 128,
641 					B_TRANSLATE("Error writing audio frame %" B_PRId64), i);
642 				status.UnlockBuffer();
643 				SetStatusMessage(status.String());
644 
645 				break;
646 			}
647 			completePercent = (float)(i - start) / (float)(end - start) * 100;
648 			currPercent = (int32)completePercent;
649 			if (currPercent > lastPercent) {
650 				lastPercent = currPercent;
651 				snprintf(status.LockBuffer(128), 128,
652 					B_TRANSLATE("Writing audio track: %" B_PRId32 "%% complete"),
653 					currPercent);
654 				status.UnlockBuffer();
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