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