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