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
32const char APP_SIGNATURE[] = "application/x-vnd.Haiku-MediaConverter";
33
34
35MediaConverterApp::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
48MediaConverterApp::~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
61void
62MediaConverterApp::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
95void
96MediaConverterApp::ReadyToRun()
97{
98	fWin->Show();
99	fWin->PostMessage(INIT_FORMAT_MENUS);
100}
101
102
103void
104MediaConverterApp::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
159bool
160MediaConverterApp::IsConverting() const
161{
162	return fConverting;
163}
164
165
166void
167MediaConverterApp::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
187void
188MediaConverterApp::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
199BEntry
200MediaConverterApp::_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
232int32
233MediaConverterApp::_RunConvertEntry(void* castToMediaConverterApp)
234{
235	MediaConverterApp* app = (MediaConverterApp*)castToMediaConverterApp;
236	app->_RunConvert();
237	return 0;
238}
239
240
241void
242MediaConverterApp::_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, &videoCodec);
275				BDirectory directory = fWin->OutputDirectory();
276				fWin->Unlock();
277				outEntry = _CreateOutputFile(directory, &inRef, fileFormat);
278
279				// display file name
280
281				outEntry.GetPath(&path);
282				name.SetTo(path.Leaf());
283
284				if (outEntry.InitCheck() == B_OK) {
285					entry_ref outRef;
286					outEntry.GetRef(&outRef);
287					outFile = new BMediaFile(&outRef, fileFormat);
288
289					BString tmp(
290						B_TRANSLATE("Output file '%filename' created"));
291					tmp.ReplaceAll("%filename", name);
292					name = tmp;
293				} else {
294					BString tmp(B_TRANSLATE("Error creating '%filename'"));
295					tmp.ReplaceAll("%filename", name);
296					name = tmp;
297				}
298
299				if (fWin->Lock()) {
300					fWin->SetFileMessage(name.String());
301					fWin->Unlock();
302				}
303
304				if (outFile != NULL) {
305					r = _ConvertFile(inFile, outFile, audioCodec, videoCodec,
306						audioQuality, videoQuality, start, end);
307
308					// set mime
309					update_mime_info(path.Path(), false, false, false);
310
311					fWin->Lock();
312					if (r == B_OK) {
313						fWin->RemoveSourceFile(srcIndex);
314					} else {
315						srcIndex++;
316						BString error(
317							B_TRANSLATE("Error converting '%filename'"));
318  						error.ReplaceAll("%filename", inRef.name);
319						fWin->SetStatusMessage(error.String());
320					}
321					fWin->Unlock();
322				}
323
324
325			} else {
326				fWin->Unlock();
327				break;
328			}
329		} else {
330			break;
331		}
332	}
333
334	BMessenger(this).SendMessage(CONVERSION_DONE_MESSAGE);
335}
336
337
338// #pragma mark -
339
340
341status_t
342MediaConverterApp::_ConvertFile(BMediaFile* inFile, BMediaFile* outFile,
343	media_codec_info* audioCodec, media_codec_info* videoCodec,
344	int32 audioQuality, int32 videoQuality,
345	bigtime_t startDuration, bigtime_t endDuration)
346{
347	BMediaTrack* inVidTrack = NULL;
348	BMediaTrack* inAudTrack = NULL;
349	BMediaTrack* outVidTrack = NULL;
350	BMediaTrack* outAudTrack = NULL;
351
352	media_format inFormat;
353	media_format outAudFormat;
354	media_format outVidFormat;
355
356	media_raw_audio_format* raf = NULL;
357	media_raw_video_format* rvf = NULL;
358
359	int32 width = -1;
360	int32 height = -1;
361
362	uint8* videoBuffer = NULL;
363	uint8* audioBuffer = NULL;
364
365	// gather the necessary format information and construct output tracks
366	int64 videoFrameCount = 0;
367	int64 audioFrameCount = 0;
368
369	status_t ret = B_OK;
370
371	int32 tracks = inFile->CountTracks();
372	for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) {
373		BMediaTrack* inTrack = inFile->TrackAt(i);
374		memset(&inFormat, 0, sizeof(media_format));
375		inTrack->EncodedFormat(&inFormat);
376		if (inFormat.IsAudio() && (audioCodec != NULL)) {
377			inAudTrack = inTrack;
378			memset(&outAudFormat, 0, sizeof(media_format));
379			outAudFormat.type = B_MEDIA_RAW_AUDIO;
380			raf = &(outAudFormat.u.raw_audio);
381			inTrack->DecodedFormat(&outAudFormat);
382
383			audioBuffer = new uint8[raf->buffer_size];
384//			audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
385//			audioFrameSize = (raf->format & 0xf) * raf->channel_count;
386			outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec);
387
388			if (outAudTrack != NULL) {
389				if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK
390					&& fWin->Lock()) {
391					fWin->SetAudioQualityLabel(
392						B_TRANSLATE("Audio quality not supported"));
393					fWin->Unlock();
394				}
395			}
396
397		} else if (inFormat.IsVideo() && (videoCodec != NULL)) {
398			inVidTrack = inTrack;
399			width = (int32)inFormat.Width();
400			height = (int32)inFormat.Height();
401
402			// construct desired decoded video format
403			memset(&outVidFormat, 0, sizeof(outVidFormat));
404			outVidFormat.type = B_MEDIA_RAW_VIDEO;
405			rvf = &(outVidFormat.u.raw_video);
406			rvf->last_active = (uint32)(height - 1);
407			rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT;
408			rvf->display.format = B_RGB32;
409			rvf->display.bytes_per_row = 4 * width;
410			rvf->display.line_width = width;
411			rvf->display.line_count = height;
412
413			inVidTrack->DecodedFormat(&outVidFormat);
414
415			if (rvf->display.format == B_RGBA32) {
416				printf("fixing color space (B_RGBA32 -> B_RGB32)");
417				rvf->display.format = B_RGB32;
418			}
419			// Transfer the display aspect ratio.
420			if (inFormat.type == B_MEDIA_ENCODED_VIDEO) {
421				rvf->pixel_width_aspect
422					= inFormat.u.encoded_video.output.pixel_width_aspect;
423				rvf->pixel_height_aspect
424					= inFormat.u.encoded_video.output.pixel_height_aspect;
425			} else {
426				rvf->pixel_width_aspect
427					= inFormat.u.raw_video.pixel_width_aspect;
428				rvf->pixel_height_aspect
429					= inFormat.u.raw_video.pixel_height_aspect;
430			}
431
432			videoBuffer = new (std::nothrow) uint8[height
433				* rvf->display.bytes_per_row];
434			outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec);
435
436			if (outVidTrack != NULL) {
437				// DLM Added to use 3ivx Parameter View
438				const char* videoQualitySupport = NULL;
439				BView* encoderView = outVidTrack->GetParameterView();
440				if (encoderView) {
441					MediaEncoderWindow* encoderWin
442						= new MediaEncoderWindow(BRect(50, 50, 520, 555),
443							encoderView);
444					encoderWin->Go();
445						// blocks until the window is quit
446
447					// The quality setting is ignored by the 3ivx encoder if the
448					// view was displayed, but this method is the trigger to read
449					// all the parameter settings
450					outVidTrack->SetQuality(videoQuality / 100.0f);
451
452					// We can now delete the encoderView created for us by the encoder
453					delete encoderView;
454					encoderView = NULL;
455
456					videoQualitySupport =
457						B_TRANSLATE("Video using parameters form settings");
458				} else {
459					if (outVidTrack->SetQuality(videoQuality / 100.0f) >= B_OK)
460						videoQualitySupport =
461							B_TRANSLATE("Video quality not supported");
462				}
463				if (videoQualitySupport && fWin->Lock()) {
464					fWin->SetVideoQualityLabel(videoQualitySupport);
465					fWin->Unlock();
466				}
467			}
468		} else {
469			//  didn't do anything with the track
470			inFile->ReleaseTrack(inTrack);
471		}
472	}
473
474	if (!outVidTrack && !outAudTrack) {
475		printf("MediaConverterApp::_ConvertFile() - no tracks found!\n");
476		ret = B_ERROR;
477	}
478
479	if (fCancel) {
480		// don't have any video or audio tracks here, or cancelled
481		printf("MediaConverterApp::_ConvertFile()"
482				" - user canceled before transcoding\n");
483		ret = B_CANCELED;
484	}
485
486	if (ret < B_OK) {
487		delete[] audioBuffer;
488		delete[] videoBuffer;
489		delete outFile;
490		return ret;
491	}
492
493	outFile->CommitHeader();
494	// this is where you would call outFile->AddCopyright(...)
495
496	int64 framesRead;
497	media_header mh;
498	int32 lastPercent, currPercent;
499	float completePercent;
500	BString status;
501
502	int64 start;
503	int64 end;
504	int32 stat = 0;
505
506	// read video from source and write to destination, if necessary
507	if (outVidTrack != NULL) {
508		lastPercent = -1;
509		videoFrameCount = inVidTrack->CountFrames();
510		if (endDuration == 0 || endDuration < startDuration) {
511			start = 0;
512			end = videoFrameCount;
513		} else {
514			inVidTrack->SeekToTime(&endDuration, stat);
515			end = inVidTrack->CurrentFrame();
516			inVidTrack->SeekToTime(&startDuration, stat);
517			start = inVidTrack->CurrentFrame();
518			if (end > videoFrameCount)
519				end =  videoFrameCount;
520			if (start > end)
521				start = 0;
522		}
523
524		framesRead = 0;
525		for (int64 i = start; (i <= end) && !fCancel; i += framesRead) {
526			if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead,
527					&mh)) != B_OK) {
528				fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n", i,
529						strerror(ret));
530				snprintf(status.LockBuffer(128), 128,
531						B_TRANSLATE("Error read video frame %" B_PRId64), i);
532				status.UnlockBuffer();
533				SetStatusMessage(status.String());
534
535				break;
536			}
537
538			if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead,
539					mh.u.encoded_video.field_flags)) != B_OK) {
540				fprintf(stderr, "Error writing video frame %" B_PRId64 ": %s\n", i,
541						strerror(ret));
542				snprintf(status.LockBuffer(128), 128,
543						B_TRANSLATE("Error writing video frame %" B_PRId64), i);
544				status.UnlockBuffer();
545				SetStatusMessage(status.String());
546
547				break;
548			}
549			completePercent = (float)(i - start) / (float)(end - start) * 100;
550			currPercent = (int32)completePercent;
551			if (currPercent > lastPercent) {
552				lastPercent = currPercent;
553				snprintf(status.LockBuffer(128), 128,
554					B_TRANSLATE("Writing video track: %" B_PRId32 "%% complete"),
555					currPercent);
556				status.UnlockBuffer();
557				SetStatusMessage(status.String());
558
559			}
560		}
561		outVidTrack->Flush();
562		inFile->ReleaseTrack(inVidTrack);
563	}
564
565	// read audio from source and write to destination, if necessary
566	if (outAudTrack != NULL) {
567		lastPercent = -1;
568
569		audioFrameCount =  inAudTrack->CountFrames();
570
571		if (endDuration == 0 || endDuration < startDuration) {
572			start = 0;
573			end = audioFrameCount;
574		} else {
575			inAudTrack->SeekToTime(&endDuration, stat);
576			end = inAudTrack->CurrentFrame();
577			inAudTrack->SeekToTime(&startDuration, stat);
578			start = inAudTrack->CurrentFrame();
579			if (end > audioFrameCount)
580				end = audioFrameCount;
581			if (start > end)
582				start = 0;
583		}
584
585		for (int64 i = start; (i <= end) && !fCancel; i += framesRead) {
586			if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead,
587				&mh)) != B_OK) {
588				fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret));
589				snprintf(status.LockBuffer(128), 128,
590					B_TRANSLATE("Error read audio frame %" B_PRId64), i);
591				status.UnlockBuffer();
592				SetStatusMessage(status.String());
593
594				break;
595			}
596
597			if ((ret = outAudTrack->WriteFrames(audioBuffer,
598				framesRead)) != B_OK) {
599				fprintf(stderr, "Error writing audio frames: %s\n",	strerror(ret));
600				snprintf(status.LockBuffer(128), 128,
601					B_TRANSLATE("Error writing audio frame %" B_PRId64), i);
602				status.UnlockBuffer();
603				SetStatusMessage(status.String());
604
605				break;
606			}
607			completePercent = (float)(i - start) / (float)(end - start) * 100;
608			currPercent = (int32)completePercent;
609			if (currPercent > lastPercent) {
610				lastPercent = currPercent;
611				snprintf(status.LockBuffer(128), 128,
612					B_TRANSLATE("Writing audio track: %" B_PRId32 "%% complete"),
613					currPercent);
614				status.UnlockBuffer();
615				SetStatusMessage(status.String());
616			}
617		}
618		outAudTrack->Flush();
619		inFile->ReleaseTrack(inAudTrack);
620
621	}
622
623	outFile->CloseFile();
624	delete outFile;
625
626	delete[] videoBuffer;
627	delete[] audioBuffer;
628
629	return ret;
630}
631
632
633// #pragma mark -
634
635
636int
637main(int, char **)
638{
639	MediaConverterApp app;
640	app.Run();
641
642	return 0;
643}
644