xref: /haiku/src/apps/mediaconverter/MediaConverterApp.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
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 using std::nothrow;
27 
28 const char APP_SIGNATURE[] = "application/x-vnd.Haiku-MediaConverter";
29 
30 
31 MediaConverterApp::MediaConverterApp()
32 	: BApplication(APP_SIGNATURE)
33 	, fWin(NULL)
34 	, fConvertThreadID(-1)
35 	, fConverting(false)
36 	, fCancel(false)
37 {
38 	// TODO: implement settings for window pos
39 	fWin = new MediaConverterWindow(BRect(50, 50, 520, 555));
40 }
41 
42 
43 MediaConverterApp::~MediaConverterApp()
44 {
45 	if (fConvertThreadID >= 0) {
46 		fCancel = true;
47 		status_t exitValue;
48 		wait_for_thread(fConvertThreadID, &exitValue);
49 	}
50 }
51 
52 
53 // #pragma mark -
54 
55 
56 void
57 MediaConverterApp::MessageReceived(BMessage *msg)
58 {
59 	switch (msg->what) {
60 		case FILE_LIST_CHANGE_MESSAGE:
61 			if (fWin->Lock()) {
62 				bool enable = fWin->CountSourceFiles() > 0;
63 				fWin->SetEnabled(enable, enable);
64 				fWin->Unlock();
65 			}
66 			break;
67 
68 		case START_CONVERSION_MESSAGE:
69 			if (!fConverting)
70 				StartConverting();
71 			break;
72 
73 		case CANCEL_CONVERSION_MESSAGE:
74 			fCancel = true;
75 			break;
76 
77 		case CONVERSION_DONE_MESSAGE:
78 			fCancel = false;
79 			fConverting = false;
80 			DetachCurrentMessage();
81 			BMessenger(fWin).SendMessage(msg);
82 			break;
83 
84 		default:
85 			BApplication::MessageReceived(msg);
86 	}
87 }
88 
89 
90 void
91 MediaConverterApp::ReadyToRun()
92 {
93 	fWin->Show();
94 	fWin->PostMessage(INIT_FORMAT_MENUS);
95 }
96 
97 void
98 MediaConverterApp::RefsReceived(BMessage *msg)
99 {
100 	entry_ref ref;
101 	int32 i = 0;
102 	BMediaFile *f;
103 	BString errorFiles;
104 	int32 errors = 0;
105 
106 	// from Open dialog or drag & drop
107 
108 	while (msg->FindRef("refs", i++, &ref) == B_OK) {
109 		f = new BMediaFile(&ref/*, B_MEDIA_FILE_NO_READ_AHEAD*/);
110 		if (f->InitCheck() != B_OK) {
111 			errorFiles << ref.name << "\n";
112 			errors++;
113 			delete f;
114 			continue;
115 		}
116 		if (fWin->Lock()) {
117 			fWin->AddSourceFile(f, ref);
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->pixel_width_aspect = 1;
388 			rvf->pixel_height_aspect = 1;
389 			rvf->display.format = B_RGB32;
390 			rvf->display.bytes_per_row = 4 * width;
391 			rvf->display.line_width = width;
392 			rvf->display.line_count = height;
393 
394 			inVidTrack->DecodedFormat(&outVidFormat);
395 
396 			if (rvf->display.format == B_RGBA32) {
397 				printf("fixing color space (B_RGBA32 -> B_RGB32)");
398 				rvf->display.format = B_RGB32;
399 			}
400 
401 			videoBuffer = new (nothrow) uint8[height * rvf->display.bytes_per_row];
402 			outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec);
403 
404 			if (outVidTrack != NULL) {
405 				// DLM Added to use 3ivx Parameter View
406 				const char* videoQualitySupport = NULL;
407 				BView* encoderView = outVidTrack->GetParameterView();
408 				if (encoderView) {
409 					MediaEncoderWindow* encoderWin
410 						= new MediaEncoderWindow(BRect(50, 50, 520, 555), encoderView);
411 					encoderWin->Go();
412 						// blocks until the window is quit
413 
414 					// The quality setting is ignored by the 3ivx encoder if the
415 					// view was displayed, but this method is the trigger to read
416 					// all the parameter settings
417 					outVidTrack->SetQuality(videoQuality / 100.0f);
418 
419 					// We can now delete the encoderView created for us by the encoder
420 					delete encoderView;
421 					encoderView = NULL;
422 
423 					videoQualitySupport = VIDEO_PARAMFORM_STRING;
424 				} else {
425 					if (outVidTrack->SetQuality(videoQuality / 100.0f) >= B_OK)
426 						videoQualitySupport = VIDEO_SUPPORT_STRING;
427 				}
428 				if (videoQualitySupport && fWin->Lock()) {
429 					fWin->SetVideoQualityLabel(videoQualitySupport);
430 					fWin->Unlock();
431 				}
432 			}
433 		} else {
434 			//  didn't do anything with the track
435 			inFile->ReleaseTrack(inTrack);
436 		}
437 	}
438 
439 	if (!outVidTrack && !outAudTrack) {
440 		printf("MediaConverterApp::_ConvertFile() - no tracks found!\n");
441 		ret = B_ERROR;
442 	}
443 
444 	if (fCancel) {
445 		// don't have any video or audio tracks here, or cancelled
446 		printf("MediaConverterApp::_ConvertFile() - user canceld before transcoding\n");
447 		ret = B_CANCELED;
448 	}
449 
450 	if (ret < B_OK) {
451 		delete[] audioBuffer;
452 		delete[] videoBuffer;
453 		delete outFile;
454 		return ret;
455 	}
456 
457 	outFile->CommitHeader();
458 	// this is where you would call outFile->AddCopyright(...)
459 
460 	int64 framesRead;
461 	media_header mh;
462 	int32 lastPercent, currPercent;
463 	float completePercent;
464 	BString status;
465 
466 	int64 start;
467 	int64 end;
468 	int32 stat = 0;
469 
470 	// read video from source and write to destination, if necessary
471 	if (outVidTrack != NULL) {
472 		lastPercent = -1;
473 		videoFrameCount = inVidTrack->CountFrames();
474 		if (endDuration == 0 || endDuration < startDuration) {
475 			start = 0;
476 			end = videoFrameCount;
477 		} else {
478 			inVidTrack->SeekToTime(&endDuration, stat);
479 			end = inVidTrack->CurrentFrame();
480 			inVidTrack->SeekToTime(&startDuration, stat);
481 			start = inVidTrack->CurrentFrame();
482 			if (end > videoFrameCount)
483 				end =  videoFrameCount;
484 			if (start > end)
485 				start = 0;
486 		}
487 
488 		framesRead = 0;
489 		for (int64 i = start; (i <= end) && !fCancel; i += framesRead) {
490 			if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead,
491 					&mh)) != B_OK) {
492 				fprintf(stderr, "Error reading video frame %Ld: %s\n", i,
493 						strerror(ret));
494 				status.SetTo(ERROR_READ_VIDEO_STRING);
495 				status << i;
496 				SetStatusMessage(status.String());
497 
498 				break;
499 			}
500 //printf("writing frame %lld\n", i);
501 			if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead,
502 					mh.u.encoded_video.field_flags)) != B_OK) {
503 				fprintf(stderr, "Error writing video frame %Ld: %s\n", i,
504 						strerror(ret));
505 				status.SetTo(ERROR_WRITE_VIDEO_STRING);
506 				status << i;
507 				SetStatusMessage(status.String());
508 				break;
509 			}
510 			completePercent = (float)(i - start) / (float)(end - start) * 100;
511 			currPercent = (int16)floor(completePercent);
512 			if (currPercent > lastPercent) {
513 				lastPercent = currPercent;
514 				status.SetTo(WRITE_VIDEO_STRING);
515 				status.Append(" ");
516 				status << currPercent << "% " << COMPLETE_STRING;
517 				SetStatusMessage(status.String());
518 
519 			}
520 		}
521 		outVidTrack->Flush();
522 		inFile->ReleaseTrack(inVidTrack);
523 	}
524 
525 	// read audio from source and write to destination, if necessary
526 	if (outAudTrack != NULL) {
527 		lastPercent = -1;
528 
529 		audioFrameCount =  inAudTrack->CountFrames();
530 
531 		if (endDuration == 0 || endDuration < startDuration) {
532 			start = 0;
533 			end = audioFrameCount;
534 		} else {
535 			inAudTrack->SeekToTime(&endDuration, stat);
536 			end = inAudTrack->CurrentFrame();
537 			inAudTrack->SeekToTime(&startDuration, stat);
538 			start = inAudTrack->CurrentFrame();
539 			if (end > audioFrameCount)
540 				end = audioFrameCount;
541 			if (start > end)
542 				start = 0;
543 		}
544 
545 		for (int64 i = start; (i <= end) && !fCancel; i += framesRead) {
546 			if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead,
547 				&mh)) != B_OK) {
548 				fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret));
549 				status.SetTo(ERROR_READ_AUDIO_STRING);
550 				status << i;
551 				SetStatusMessage(status.String());
552 				break;
553 			}
554 //printf("writing audio frames %lld\n", i);
555 			if ((ret = outAudTrack->WriteFrames(audioBuffer,
556 				framesRead)) != B_OK) {
557 				fprintf(stderr, "Error writing audio frames: %s\n",
558 					strerror(ret));
559 				status.SetTo(ERROR_WRITE_AUDIO_STRING);
560 				status << i;
561 				SetStatusMessage(status.String());
562 				break;
563 			}
564 			completePercent = (float)(i - start) / (float)(end - start) * 100;
565 			currPercent = (int16)floor(completePercent);
566 			if (currPercent > lastPercent) {
567 				lastPercent = currPercent;
568 				status.SetTo(WRITE_AUDIO_STRING);
569 				status.Append(" ");
570 				status << currPercent << "% " << COMPLETE_STRING;
571 				SetStatusMessage(status.String());
572 			}
573 		}
574 		outAudTrack->Flush();
575 		inFile->ReleaseTrack(inAudTrack);
576 
577 	}
578 
579 	outFile->CloseFile();
580 	delete outFile;
581 
582 	delete[] videoBuffer;
583 	delete[] audioBuffer;
584 
585 	return ret;
586 }
587 
588 
589 // #pragma mark -
590 
591 
592 int
593 main(int, char **)
594 {
595 	MediaConverterApp app;
596 	app.Run();
597 
598 	return 0;
599 }
600