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