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