forked from mia/Aegisub
FFmpegSource 1.16 + minor fix
Originally committed to SVN as r2151.
This commit is contained in:
parent
b7af0a06d5
commit
d6e76cee4f
13 changed files with 114 additions and 310 deletions
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -29,7 +29,7 @@ int FFBase::FrameFromDTS(int64_t ADTS) {
|
||||||
|
|
||||||
int FFBase::ClosestFrameFromDTS(int64_t ADTS) {
|
int FFBase::ClosestFrameFromDTS(int64_t ADTS) {
|
||||||
int Frame = 0;
|
int Frame = 0;
|
||||||
int64_t BestDiff = 0xFFFFFFFFFFFFFFLL;
|
int64_t BestDiff = 0xFFFFFFFFFFFFFFLL; // big number
|
||||||
for (int i = 0; i < (int)Frames.size(); i++) {
|
for (int i = 0; i < (int)Frames.size(); i++) {
|
||||||
int64_t CurrentDiff = FFABS(Frames[i].DTS - ADTS);
|
int64_t CurrentDiff = FFABS(Frames[i].DTS - ADTS);
|
||||||
if (CurrentDiff < BestDiff) {
|
if (CurrentDiff < BestDiff) {
|
||||||
|
@ -113,83 +113,6 @@ bool FFBase::SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int6
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
static FLAC__StreamDecoderReadStatus FLACStreamDecoderReadCallback(const FLAC__StreamDecoder *ADecoder, FLAC__byte ABuffer[], size_t *ABytes, FFBase *AOwner) {
|
|
||||||
if(*ABytes > 0) {
|
|
||||||
*ABytes = fread(ABuffer, sizeof(FLAC__byte), *ABytes, AOwner->FCFile);
|
|
||||||
if(ferror(AOwner->FCFile))
|
|
||||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
|
||||||
else if(*ABytes == 0)
|
|
||||||
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
|
||||||
else
|
|
||||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
|
||||||
} else {
|
|
||||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static FLAC__StreamDecoderSeekStatus FLACStreamDecoderSeekCallback(const FLAC__StreamDecoder *ADecoder, FLAC__uint64 AAbsoluteByteOffset, FFBase *AOwner) {
|
|
||||||
if(_fseeki64(AOwner->FCFile, AAbsoluteByteOffset, SEEK_SET) < 0)
|
|
||||||
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
|
||||||
else
|
|
||||||
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FLAC__StreamDecoderTellStatus FLACStreamDecoderTellCallback(const FLAC__StreamDecoder *ADecoder, FLAC__uint64 *AAbsoluteByteOffset, FFBase *AOwner) {
|
|
||||||
__int64 Pos;
|
|
||||||
if ((Pos = _ftelli64(AOwner->FCFile)) < 0) {
|
|
||||||
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
|
|
||||||
} else {
|
|
||||||
*AAbsoluteByteOffset = (FLAC__uint64)Pos;
|
|
||||||
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static FLAC__StreamDecoderLengthStatus FLACStreamDecoderLengthCallback(const FLAC__StreamDecoder *ADecoder, FLAC__uint64 *AStreamLength, FFBase *AOwner) {
|
|
||||||
__int64 OriginalPos;
|
|
||||||
__int64 Length;
|
|
||||||
if ((OriginalPos = _ftelli64(AOwner->FCFile)) < 0)
|
|
||||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
|
||||||
_fseeki64(AOwner->FCFile, 0, SEEK_END);
|
|
||||||
if ((Length = _ftelli64(AOwner->FCFile)) < 0)
|
|
||||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
|
|
||||||
_fseeki64(AOwner->FCFile, OriginalPos, SEEK_SET);
|
|
||||||
*AStreamLength = Length;
|
|
||||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FLAC__bool FLACStreamDecoderEofCallback(const FLAC__StreamDecoder *ADecoder, FFBase *AOwner) {
|
|
||||||
return feof(AOwner->FCFile) ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FLAC__StreamDecoderWriteStatus FLACStreamDecoderWriteCallback(const FLAC__StreamDecoder *ADecoder, const FLAC__Frame *AFrame, const FLAC__int32 *const ABuffer[], FFBase *AOwner) {
|
|
||||||
unsigned Blocksize = AFrame->header.blocksize;
|
|
||||||
const VideoInfo VI = AOwner->GetVideoInfo();
|
|
||||||
int16_t *Buffer = (int16_t *)AOwner->FCBuffer;
|
|
||||||
|
|
||||||
int j = 0;
|
|
||||||
while (AOwner->FCCount > 0 && Blocksize > 0) {
|
|
||||||
for (int i = 0; i < VI.nchannels; i++)
|
|
||||||
*Buffer++ = ABuffer[i][j];
|
|
||||||
j++;
|
|
||||||
AOwner->FCCount--;
|
|
||||||
Blocksize--;
|
|
||||||
}
|
|
||||||
|
|
||||||
AOwner->FCBuffer = Buffer;
|
|
||||||
|
|
||||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void FLACStreamDecoderMetadataCallback(const FLAC__StreamDecoder *ADecoder, const FLAC__StreamMetadata *AMetadata, FFBase *AOwner) {
|
|
||||||
AOwner->FCError = (AMetadata->data.stream_info.total_samples <= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void FLACStreamDecoderErrorCallback(const FLAC__StreamDecoder *ADecoder, FLAC__StreamDecoderErrorStatus AStatus, FFBase *AOwner) {
|
|
||||||
AOwner->FCError = true;
|
|
||||||
}
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
|
|
||||||
bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
||||||
char DefaultCacheFilename[1024];
|
char DefaultCacheFilename[1024];
|
||||||
sprintf(DefaultCacheFilename, "%s.ffa%dcache", ASource, AAudioTrack);
|
sprintf(DefaultCacheFilename, "%s.ffa%dcache", ASource, AAudioTrack);
|
||||||
|
@ -197,7 +120,7 @@ bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, in
|
||||||
AAudioCacheFile = DefaultCacheFilename;
|
AAudioCacheFile = DefaultCacheFilename;
|
||||||
|
|
||||||
// Is an empty file?
|
// Is an empty file?
|
||||||
FCFile = fopen(AAudioCacheFile, "rb");
|
FILE *FCFile = fopen(AAudioCacheFile, "rb");
|
||||||
int64_t CacheSize;
|
int64_t CacheSize;
|
||||||
if (FCFile) {
|
if (FCFile) {
|
||||||
_fseeki64(FCFile, 0, SEEK_END);
|
_fseeki64(FCFile, 0, SEEK_END);
|
||||||
|
@ -212,29 +135,6 @@ bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, in
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
// is FLAC?
|
|
||||||
FLACAudioCache = FLAC__stream_decoder_new();
|
|
||||||
if (FLAC__stream_decoder_init_stream(FLACAudioCache,
|
|
||||||
&(FLAC__StreamDecoderReadCallback)FLACStreamDecoderReadCallback,
|
|
||||||
&(FLAC__StreamDecoderSeekCallback)FLACStreamDecoderSeekCallback,
|
|
||||||
&(FLAC__StreamDecoderTellCallback)FLACStreamDecoderTellCallback,
|
|
||||||
&(FLAC__StreamDecoderLengthCallback)FLACStreamDecoderLengthCallback,
|
|
||||||
&(FLAC__StreamDecoderEofCallback)FLACStreamDecoderEofCallback,
|
|
||||||
&(FLAC__StreamDecoderWriteCallback)FLACStreamDecoderWriteCallback,
|
|
||||||
&(FLAC__StreamDecoderMetadataCallback)FLACStreamDecoderMetadataCallback, &(FLAC__StreamDecoderErrorCallback)FLACStreamDecoderErrorCallback, this) == FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
|
||||||
FCError = true;
|
|
||||||
FLAC__stream_decoder_process_until_end_of_metadata(FLACAudioCache);
|
|
||||||
if (!FCError) {
|
|
||||||
VI.num_audio_samples = FLAC__stream_decoder_get_total_samples(FLACAudioCache);
|
|
||||||
AudioCacheType = acFLAC;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FLAC__stream_decoder_delete(FLACAudioCache);
|
|
||||||
FLACAudioCache = NULL;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
|
|
||||||
// Raw audio
|
// Raw audio
|
||||||
VI.num_audio_samples = VI.AudioSamplesFromBytes(CacheSize);
|
VI.num_audio_samples = VI.AudioSamplesFromBytes(CacheSize);
|
||||||
AudioCacheType = acRaw;
|
AudioCacheType = acRaw;
|
||||||
|
@ -243,64 +143,6 @@ bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, in
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
static FLAC__StreamEncoderWriteStatus FLACStreamEncoderWriteCallback(const FLAC__StreamEncoder *ASncoder, const FLAC__byte ABuffer[], size_t ABytes, unsigned ABamples, unsigned ACurrentFrame, FFBase *AOwner) {
|
|
||||||
fwrite(ABuffer, sizeof(FLAC__byte), ABytes, AOwner->FCFile);
|
|
||||||
if(ferror(AOwner->FCFile))
|
|
||||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
|
|
||||||
else
|
|
||||||
return FLAC__STREAM_ENCODER_WRITE_STATUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FLAC__StreamEncoderSeekStatus FLACStreamEncoderSeekCallback(const FLAC__StreamEncoder *AEncoder, FLAC__uint64 AAbsoluteByteOffset, FFBase *AOwner) {
|
|
||||||
if(_fseeki64(AOwner->FCFile, AAbsoluteByteOffset, SEEK_SET) < 0)
|
|
||||||
return FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR;
|
|
||||||
else
|
|
||||||
return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static FLAC__StreamEncoderTellStatus FLACStreamEncoderTellCallback(const FLAC__StreamEncoder *AEncoder, FLAC__uint64 *AAbsoluteByteOffset, FFBase *AOwner) {
|
|
||||||
__int64 Pos;
|
|
||||||
if((Pos = _ftelli64(AOwner->FCFile)) < 0) {
|
|
||||||
return FLAC__STREAM_ENCODER_TELL_STATUS_ERROR;
|
|
||||||
} else {
|
|
||||||
*AAbsoluteByteOffset = (FLAC__uint64)Pos;
|
|
||||||
return FLAC__STREAM_ENCODER_TELL_STATUS_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FLAC__StreamEncoder *FFBase::NewFLACCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, int ACompression, IScriptEnvironment *Env) {
|
|
||||||
char DefaultCacheFilename[1024];
|
|
||||||
sprintf(DefaultCacheFilename, "%s.ffa%dcache", ASource, AAudioTrack);
|
|
||||||
if (!strcmp(AAudioCacheFile, ""))
|
|
||||||
AAudioCacheFile = DefaultCacheFilename;
|
|
||||||
|
|
||||||
FCFile = fopen(AAudioCacheFile, "wb");
|
|
||||||
if (!FCFile)
|
|
||||||
Env->ThrowError("FFmpegSource: Failed to open '%s' for writing", AAudioCacheFile);
|
|
||||||
FLAC__StreamEncoder *FSE;
|
|
||||||
FSE = FLAC__stream_encoder_new();
|
|
||||||
FLAC__stream_encoder_set_channels(FSE, VI.nchannels);
|
|
||||||
FLAC__stream_encoder_set_bits_per_sample(FSE, VI.BytesPerChannelSample() * 8);
|
|
||||||
FLAC__stream_encoder_set_sample_rate(FSE, VI.audio_samples_per_second);
|
|
||||||
FLAC__stream_encoder_set_compression_level(FSE, ACompression);
|
|
||||||
if (FLAC__stream_encoder_init_stream(FSE, &(FLAC__StreamEncoderWriteCallback)FLACStreamEncoderWriteCallback,
|
|
||||||
&(FLAC__StreamEncoderSeekCallback)FLACStreamEncoderSeekCallback,
|
|
||||||
&(FLAC__StreamEncoderTellCallback)FLACStreamEncoderTellCallback, NULL, this)
|
|
||||||
!= FLAC__STREAM_ENCODER_INIT_STATUS_OK)
|
|
||||||
Env->ThrowError("FFmpegSource: Failed to initialize the FLAC encoder for '%s'", AAudioCacheFile);
|
|
||||||
|
|
||||||
return FSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FFBase::CloseFLACCacheWriter(FLAC__StreamEncoder *AFSE) {
|
|
||||||
FLAC__stream_encoder_finish(AFSE);
|
|
||||||
FLAC__stream_encoder_delete(AFSE);
|
|
||||||
fclose(FCFile);
|
|
||||||
FCFile = NULL;
|
|
||||||
}
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
|
|
||||||
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
||||||
char DefaultCacheFilename[1024];
|
char DefaultCacheFilename[1024];
|
||||||
sprintf(DefaultCacheFilename, "%s.ffa%dcache", ASource, AAudioTrack);
|
sprintf(DefaultCacheFilename, "%s.ffa%dcache", ASource, AAudioTrack);
|
||||||
|
@ -325,7 +167,7 @@ void FFBase::InitPP(int AWidth, int AHeight, const char *APPString, int AQuality
|
||||||
Env->ThrowError("FFmpegSource: Quality is out of range");
|
Env->ThrowError("FFmpegSource: Quality is out of range");
|
||||||
|
|
||||||
// Unsafe?
|
// Unsafe?
|
||||||
PPMode = pp_get_mode_by_name_and_quality((char *)APPString, AQuality);
|
PPMode = pp_get_mode_by_name_and_quality(APPString, AQuality);
|
||||||
if (!PPMode)
|
if (!PPMode)
|
||||||
Env->ThrowError("FFmpegSource: Invalid postprocesing settings");
|
Env->ThrowError("FFmpegSource: Invalid postprocesing settings");
|
||||||
|
|
||||||
|
@ -379,7 +221,7 @@ PVideoFrame FFBase::OutputFrame(AVFrame *AFrame, IScriptEnvironment *Env) {
|
||||||
AVPicture *SrcPicture = (AVPicture *)AFrame;
|
AVPicture *SrcPicture = (AVPicture *)AFrame;
|
||||||
|
|
||||||
if (PPContext) {
|
if (PPContext) {
|
||||||
pp_postprocess(AFrame->data, AFrame->linesize, PPPicture.data, PPPicture.linesize, VI.width, VI.height, AFrame->qscale_table, AFrame->qstride, PPMode, PPContext, AFrame->pict_type | (AFrame->qscale_type ? PP_PICT_TYPE_QP2 : 0));
|
pp_postprocess(const_cast<const uint8_t **>(AFrame->data), AFrame->linesize, PPPicture.data, PPPicture.linesize, VI.width, VI.height, AFrame->qscale_table, AFrame->qstride, PPMode, PPContext, AFrame->pict_type | (AFrame->qscale_type ? PP_PICT_TYPE_QP2 : 0));
|
||||||
SrcPicture = &PPPicture;
|
SrcPicture = &PPPicture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,15 +258,7 @@ PVideoFrame FFBase::OutputFrame(AVFrame *AFrame, IScriptEnvironment *Env) {
|
||||||
void FFBase::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) {
|
void FFBase::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) {
|
||||||
if (AudioCacheType == acRaw) {
|
if (AudioCacheType == acRaw) {
|
||||||
_fseeki64(RawAudioCache, VI.BytesFromAudioSamples(Start), SEEK_SET);
|
_fseeki64(RawAudioCache, VI.BytesFromAudioSamples(Start), SEEK_SET);
|
||||||
fread(Buf, 1, (size_t)VI.BytesFromAudioSamples(Count), RawAudioCache);
|
fread(Buf, 1, static_cast<size_t>(VI.BytesFromAudioSamples(Count)), RawAudioCache);
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
} else if (AudioCacheType == acFLAC) {
|
|
||||||
FCCount = Count;
|
|
||||||
FCBuffer = Buf;
|
|
||||||
FLAC__stream_decoder_seek_absolute(FLACAudioCache, Start);
|
|
||||||
while (FCCount > 0)
|
|
||||||
FLAC__stream_decoder_process_single(FLACAudioCache);
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
} else {
|
} else {
|
||||||
Env->ThrowError("FFmpegSource: Audio requested but none available");
|
Env->ThrowError("FFmpegSource: Audio requested but none available");
|
||||||
}
|
}
|
||||||
|
@ -433,18 +267,12 @@ void FFBase::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironmen
|
||||||
FFBase::FFBase() {
|
FFBase::FFBase() {
|
||||||
memset(&VI, 0, sizeof(VI));
|
memset(&VI, 0, sizeof(VI));
|
||||||
AudioCacheType = acNone;
|
AudioCacheType = acNone;
|
||||||
FCError = false;
|
|
||||||
RawAudioCache = NULL;
|
RawAudioCache = NULL;
|
||||||
PPContext = NULL;
|
PPContext = NULL;
|
||||||
PPMode = NULL;
|
PPMode = NULL;
|
||||||
SWS = NULL;
|
SWS = NULL;
|
||||||
LastFrameNum = -1;
|
LastFrameNum = -1;
|
||||||
DecodingBuffer = new uint8_t[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
DecodingBuffer = new uint8_t[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
FLACAudioCache = NULL;
|
|
||||||
FLACBuffer = new FLAC__int32[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
FCFile = NULL;
|
|
||||||
ConvertToFormat = PIX_FMT_NONE;
|
ConvertToFormat = PIX_FMT_NONE;
|
||||||
memset(&PPPicture, 0, sizeof(PPPicture));
|
memset(&PPPicture, 0, sizeof(PPPicture));
|
||||||
DecodeFrame = avcodec_alloc_frame();
|
DecodeFrame = avcodec_alloc_frame();
|
||||||
|
@ -454,15 +282,6 @@ FFBase::~FFBase() {
|
||||||
delete [] DecodingBuffer;
|
delete [] DecodingBuffer;
|
||||||
if (RawAudioCache)
|
if (RawAudioCache)
|
||||||
fclose(RawAudioCache);
|
fclose(RawAudioCache);
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
delete [] FLACBuffer;
|
|
||||||
if (FLACAudioCache) {
|
|
||||||
FLAC__stream_decoder_finish(FLACAudioCache);
|
|
||||||
FLAC__stream_decoder_delete(FLACAudioCache);
|
|
||||||
}
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
if (FCFile)
|
|
||||||
fclose(FCFile);
|
|
||||||
if (SWS)
|
if (SWS)
|
||||||
sws_freeContext(SWS);
|
sws_freeContext(SWS);
|
||||||
if (PPMode)
|
if (PPMode)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -45,8 +45,8 @@ int FFMatroskaSource::GetTrackIndex(int Index, unsigned char ATrackType, IScript
|
||||||
}
|
}
|
||||||
|
|
||||||
FFMatroskaSource::FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes,
|
FFMatroskaSource::FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes,
|
||||||
bool AVCache, const char *AVideoCache, const char *AAudioCache, int AACCompression, const char *APPString,
|
bool AVCache, const char *AVideoCache, const char *AAudioCache, const char *APPString,
|
||||||
int AQuality, IScriptEnvironment* Env, FrameInfoVector *AFrames) {
|
int AQuality, int AThreads, IScriptEnvironment* Env, FrameInfoVector *AFrames) {
|
||||||
|
|
||||||
AFrames = &Frames;
|
AFrames = &Frames;
|
||||||
CurrentFrame = 0;
|
CurrentFrame = 0;
|
||||||
|
@ -105,6 +105,7 @@ FFMatroskaSource::FFMatroskaSource(const char *ASource, int AVideoTrack, int AAu
|
||||||
VideoCodecContext = avcodec_alloc_context();
|
VideoCodecContext = avcodec_alloc_context();
|
||||||
VideoCodecContext->extradata = (uint8_t *)VideoTI->CodecPrivate;
|
VideoCodecContext->extradata = (uint8_t *)VideoTI->CodecPrivate;
|
||||||
VideoCodecContext->extradata_size = VideoTI->CodecPrivateSize;
|
VideoCodecContext->extradata_size = VideoTI->CodecPrivateSize;
|
||||||
|
VideoCodecContext->thread_count = AThreads;
|
||||||
|
|
||||||
VideoCodec = avcodec_find_decoder(MatroskaToFFCodecID(VideoTI));
|
VideoCodec = avcodec_find_decoder(MatroskaToFFCodecID(VideoTI));
|
||||||
if (VideoCodec == NULL)
|
if (VideoCodec == NULL)
|
||||||
|
@ -184,11 +185,11 @@ FFMatroskaSource::FFMatroskaSource(const char *ASource, int AVideoTrack, int AAu
|
||||||
VI.audio_samples_per_second = AudioCodecContext->sample_rate;
|
VI.audio_samples_per_second = AudioCodecContext->sample_rate;
|
||||||
|
|
||||||
switch (AudioCodecContext->sample_fmt) {
|
switch (AudioCodecContext->sample_fmt) {
|
||||||
case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; AACCompression = -1; break;
|
case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break;
|
||||||
case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break;
|
case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break;
|
||||||
case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; AACCompression = -1; break;
|
case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break;
|
||||||
case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; AACCompression = -1; break;
|
case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break;
|
||||||
case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; AACCompression = -1; break;
|
case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break;
|
||||||
default:
|
default:
|
||||||
Env->ThrowError("FFmpegSource: Unsupported/unknown sample format");
|
Env->ThrowError("FFmpegSource: Unsupported/unknown sample format");
|
||||||
}
|
}
|
||||||
|
@ -202,20 +203,11 @@ FFMatroskaSource::FFMatroskaSource(const char *ASource, int AVideoTrack, int AAu
|
||||||
|
|
||||||
// Needs to be indexed?
|
// Needs to be indexed?
|
||||||
if (!ACacheIsValid || !VCacheIsValid) {
|
if (!ACacheIsValid || !VCacheIsValid) {
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
FLAC__StreamEncoder *FSE = NULL;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
FILE *RawCache = NULL;
|
FILE *RawCache = NULL;
|
||||||
if (!ACacheIsValid)
|
if (!ACacheIsValid)
|
||||||
if (AACCompression >= 0)
|
|
||||||
AudioCacheType = acFLAC;
|
|
||||||
else
|
|
||||||
AudioCacheType = acRaw;
|
AudioCacheType = acRaw;
|
||||||
|
|
||||||
switch (AudioCacheType) {
|
switch (AudioCacheType) {
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
case acFLAC: FSE = NewFLACCacheWriter(AAudioCache, ASource, AudioTrack, AACCompression, Env); break;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,21 +236,12 @@ FFMatroskaSource::FFMatroskaSource(const char *ASource, int AVideoTrack, int AAu
|
||||||
|
|
||||||
if (AudioCacheType == acRaw) {
|
if (AudioCacheType == acRaw) {
|
||||||
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
} else if (AudioCacheType == acFLAC) {
|
|
||||||
for (int i = 0; i < DecodedSamples * VI.nchannels; i++)
|
|
||||||
FLACBuffer[i] = ((int16_t *)DecodingBuffer)[i];
|
|
||||||
FLAC__stream_encoder_process_interleaved(FSE, FLACBuffer, DecodedSamples);
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ACacheIsValid) {
|
if (!ACacheIsValid) {
|
||||||
switch (AudioCacheType) {
|
switch (AudioCacheType) {
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
case acFLAC: CloseFLACCacheWriter(FSE); break;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
case acRaw: CloseRawCacheWriter(RawCache); break;
|
case acRaw: CloseRawCacheWriter(RawCache); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -45,8 +45,8 @@ int FFmpegSource::GetTrackIndex(int Index, CodecType ATrackType, IScriptEnvironm
|
||||||
|
|
||||||
|
|
||||||
FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes,
|
FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes,
|
||||||
bool AVCache, const char *AVideoCache, const char *AAudioCache, int AACCompression, const char *APPString,
|
bool AVCache, const char *AVideoCache, const char *AAudioCache, const char *APPString,
|
||||||
int AQuality, int ASeekMode, IScriptEnvironment *Env, FrameInfoVector *AFrames) {
|
int AQuality, int AThreads, int ASeekMode, IScriptEnvironment *Env, FrameInfoVector *AFrames) {
|
||||||
|
|
||||||
AFrames = &Frames;
|
AFrames = &Frames;
|
||||||
CurrentFrame = 0;
|
CurrentFrame = 0;
|
||||||
|
@ -79,6 +79,7 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
VCacheIsValid = LoadFrameInfoFromFile(AVideoCache, ASource, VideoTrack);
|
VCacheIsValid = LoadFrameInfoFromFile(AVideoCache, ASource, VideoTrack);
|
||||||
|
|
||||||
VideoCodecContext = FormatContext->streams[VideoTrack]->codec;
|
VideoCodecContext = FormatContext->streams[VideoTrack]->codec;
|
||||||
|
VideoCodecContext->thread_count = AThreads;
|
||||||
|
|
||||||
VideoCodec = avcodec_find_decoder(VideoCodecContext->codec_id);
|
VideoCodec = avcodec_find_decoder(VideoCodecContext->codec_id);
|
||||||
if (VideoCodec == NULL)
|
if (VideoCodec == NULL)
|
||||||
|
@ -124,11 +125,11 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
Env->ThrowError("FFmpegSource: Could not open audio codec");
|
Env->ThrowError("FFmpegSource: Could not open audio codec");
|
||||||
|
|
||||||
switch (AudioCodecContext->sample_fmt) {
|
switch (AudioCodecContext->sample_fmt) {
|
||||||
case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; AACCompression = -1; break;
|
case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; break;
|
||||||
case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break;
|
case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break;
|
||||||
case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; AACCompression = -1; break;
|
case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; break;
|
||||||
case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; AACCompression = -1; break;
|
case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; break;
|
||||||
case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; AACCompression = -1; break;
|
case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; break;
|
||||||
default:
|
default:
|
||||||
Env->ThrowError("FFmpegSource: Unsupported/unknown sample format");
|
Env->ThrowError("FFmpegSource: Unsupported/unknown sample format");
|
||||||
}
|
}
|
||||||
|
@ -144,20 +145,11 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
|
|
||||||
// Needs to be indexed?
|
// Needs to be indexed?
|
||||||
if (!ACacheIsValid || !VCacheIsValid) {
|
if (!ACacheIsValid || !VCacheIsValid) {
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
FLAC__StreamEncoder *FSE = NULL;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
FILE *RawCache = NULL;
|
FILE *RawCache = NULL;
|
||||||
if (!ACacheIsValid)
|
if (!ACacheIsValid)
|
||||||
if (AACCompression >= 0)
|
|
||||||
AudioCacheType = acFLAC;
|
|
||||||
else
|
|
||||||
AudioCacheType = acRaw;
|
AudioCacheType = acRaw;
|
||||||
|
|
||||||
switch (AudioCacheType) {
|
switch (AudioCacheType) {
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
case acFLAC: FSE = NewFLACCacheWriter(AAudioCache, ASource, AudioTrack, AACCompression, Env); break;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,12 +176,6 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
|
|
||||||
if (AudioCacheType == acRaw) {
|
if (AudioCacheType == acRaw) {
|
||||||
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
} else if (AudioCacheType == acFLAC) {
|
|
||||||
for (int i = 0; i < DecodedSamples * VI.nchannels; i++)
|
|
||||||
FLACBuffer[i] = ((int16_t *)DecodingBuffer)[i];
|
|
||||||
FLAC__stream_encoder_process_interleaved(FSE, FLACBuffer, DecodedSamples);
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,9 +185,6 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
|
|
||||||
if (!ACacheIsValid) {
|
if (!ACacheIsValid) {
|
||||||
switch (AudioCacheType) {
|
switch (AudioCacheType) {
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
case acFLAC: CloseFLACCacheWriter(FSE); break;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
case acRaw: CloseRawCacheWriter(RawCache); break;
|
case acRaw: CloseRawCacheWriter(RawCache); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -21,10 +21,6 @@
|
||||||
#ifndef FFMPEGSOURCE_H
|
#ifndef FFMPEGSOURCE_H
|
||||||
#define FFMPEGSOURCE_H
|
#define FFMPEGSOURCE_H
|
||||||
|
|
||||||
#ifndef NO_FLAC_CACHE
|
|
||||||
//#define FLAC_CACHE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -35,16 +31,11 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
|
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
#include <stream_decoder.h>
|
|
||||||
#include <stream_encoder.h>
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <ffmpeg\avformat.h>
|
#include <libavformat/avformat.h>
|
||||||
#include <ffmpeg\avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
#include <ffmpeg\swscale.h>
|
#include <libswscale/swscale.h>
|
||||||
#include <postproc\postprocess.h>
|
#include <libpostproc/postprocess.h>
|
||||||
|
|
||||||
#include "stdiostream.h"
|
#include "stdiostream.h"
|
||||||
}
|
}
|
||||||
|
@ -52,7 +43,7 @@ extern "C" {
|
||||||
#include "MatroskaParser.h"
|
#include "MatroskaParser.h"
|
||||||
#include "avisynth.h"
|
#include "avisynth.h"
|
||||||
|
|
||||||
enum AudioCacheFormat {acNone, acRaw, acFLAC};
|
enum AudioCacheFormat {acNone, acRaw};
|
||||||
|
|
||||||
struct FrameInfo {
|
struct FrameInfo {
|
||||||
int64_t DTS;
|
int64_t DTS;
|
||||||
|
@ -111,11 +102,6 @@ protected:
|
||||||
int LastFrameNum;
|
int LastFrameNum;
|
||||||
uint8_t *DecodingBuffer;
|
uint8_t *DecodingBuffer;
|
||||||
|
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
FLAC__StreamDecoder *FLACAudioCache;
|
|
||||||
FLAC__int32 *FLACBuffer;
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
|
|
||||||
FrameInfoVector Frames;
|
FrameInfoVector Frames;
|
||||||
|
|
||||||
int FindClosestKeyFrame(int AFrame);
|
int FindClosestKeyFrame(int AFrame);
|
||||||
|
@ -126,10 +112,6 @@ protected:
|
||||||
bool SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int64_t ScaleN);
|
bool SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int64_t ScaleN);
|
||||||
|
|
||||||
bool OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
bool OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
||||||
#ifdef FLAC_CACHE
|
|
||||||
FLAC__StreamEncoder *FFBase::NewFLACCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, int ACompression, IScriptEnvironment *Env);
|
|
||||||
void FFBase::CloseFLACCacheWriter(FLAC__StreamEncoder *AFSE);
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
||||||
void FFBase::CloseRawCacheWriter(FILE *ARawCache);
|
void FFBase::CloseRawCacheWriter(FILE *ARawCache);
|
||||||
|
|
||||||
|
@ -137,11 +119,6 @@ protected:
|
||||||
void SetOutputFormat(int ACurrentFormat, IScriptEnvironment *Env);
|
void SetOutputFormat(int ACurrentFormat, IScriptEnvironment *Env);
|
||||||
PVideoFrame OutputFrame(AVFrame *AFrame, IScriptEnvironment *Env);
|
PVideoFrame OutputFrame(AVFrame *AFrame, IScriptEnvironment *Env);
|
||||||
public:
|
public:
|
||||||
// FLAC decoder variables, have to be public
|
|
||||||
FILE *FCFile;
|
|
||||||
__int64 FCCount;
|
|
||||||
void *FCBuffer;
|
|
||||||
bool FCError;
|
|
||||||
|
|
||||||
FFBase();
|
FFBase();
|
||||||
~FFBase();
|
~FFBase();
|
||||||
|
@ -164,7 +141,7 @@ private:
|
||||||
int GetTrackIndex(int Index, CodecType ATrackType, IScriptEnvironment *Env);
|
int GetTrackIndex(int Index, CodecType ATrackType, IScriptEnvironment *Env);
|
||||||
int DecodeNextFrame(AVFrame *Frame, int64_t *DTS);
|
int DecodeNextFrame(AVFrame *Frame, int64_t *DTS);
|
||||||
public:
|
public:
|
||||||
FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, int AACCompression, const char *APPString, int AQuality, int ASeekMode, IScriptEnvironment *Env, FrameInfoVector *AFrames);
|
FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, const char *APPString, int AQuality, int AThreads, int ASeekMode, IScriptEnvironment *Env, FrameInfoVector *AFrames);
|
||||||
~FFmpegSource();
|
~FFmpegSource();
|
||||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env);
|
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env);
|
||||||
};
|
};
|
||||||
|
@ -185,7 +162,7 @@ private:
|
||||||
int DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, IScriptEnvironment* Env);
|
int DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, IScriptEnvironment* Env);
|
||||||
int GetTrackIndex(int Index, unsigned char ATrackType, IScriptEnvironment *Env);
|
int GetTrackIndex(int Index, unsigned char ATrackType, IScriptEnvironment *Env);
|
||||||
public:
|
public:
|
||||||
FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, int AACCompression, const char *APPString, int AQuality, IScriptEnvironment *Env, FrameInfoVector *AFrames);
|
FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, const char *APPString, int AQuality, int AThreads, IScriptEnvironment *Env, FrameInfoVector *AFrames);
|
||||||
~FFMatroskaSource();
|
~FFMatroskaSource();
|
||||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env);
|
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env);
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@ Loads video files without sucking
|
||||||
|
|
||||||
<h2>Usage</h2>
|
<h2>Usage</h2>
|
||||||
<p>
|
<p>
|
||||||
<b>FFmpegSource(string source, int vtrack = -1, int atrack = -2, string timecodes, bool vcache = true, string vcachefile, string acachefile, int accompression = -1, string pp, int ppquality = 6, int seekmode = 1)</b><br />
|
<b>FFmpegSource(string source, int vtrack = -1, int atrack = -2, string timecodes, bool vcache = true, string vcachefile, string acachefile, string pp, int ppquality = 6, int threads = 1, int seekmode = 1)</b><br />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -79,11 +79,6 @@ Separate postprocessing which also seems to include a few simple deinterlacers
|
||||||
Specifies the file to store the demuxed audio stream in. Only used by FFAudioSource if the stream isn't matroska. If nothing is specified (source).ffasd(tracknumber)cache is used.
|
Specifies the file to store the demuxed audio stream in. Only used by FFAudioSource if the stream isn't matroska. If nothing is specified (source).ffasd(tracknumber)cache is used.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
<b>accompression:</b>
|
|
||||||
Audio cache compression, -1 means raw audio and 0-8 uses FLAC with that compression level.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>pp:</b>
|
<b>pp:</b>
|
||||||
See the table below for a full description, an empty string means no processing. It is recommended to avoid the autoq option since it's currently unknown what effect it will have on the processing.
|
See the table below for a full description, an empty string means no processing. It is recommended to avoid the autoq option since it's currently unknown what effect it will have on the processing.
|
||||||
|
@ -94,6 +89,11 @@ Separate postprocessing which also seems to include a few simple deinterlacers
|
||||||
The quality to use for the specified postprocessing. Valid values are 0-6 where 0 usually means that no actual processing is done.
|
The quality to use for the specified postprocessing. Valid values are 0-6 where 0 usually means that no actual processing is done.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>threads:</b>
|
||||||
|
Sets the number of decoder threads used, ignored if the used decoder doesn't implement it.
|
||||||
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>seekmode:</b>
|
<b>seekmode:</b>
|
||||||
Force how seeking is handled, has no effect on matroska files which always use the equivalent of seekmode=1<br />
|
Force how seeking is handled, has no effect on matroska files which always use the equivalent of seekmode=1<br />
|
||||||
|
@ -154,16 +154,32 @@ tn:64:128:256
|
||||||
|
|
||||||
<p><b>FFmpeg svn</b> from http://ffmpeg.mplayerhq.hu/</p>
|
<p><b>FFmpeg svn</b> from http://ffmpeg.mplayerhq.hu/</p>
|
||||||
|
|
||||||
<p><b>FLAC (optional)</b> from http://flac.sourceforge.net/ (static library seems to be broken)</p>
|
|
||||||
|
|
||||||
<p><b>Required FFmpeg Configuration:</b>
|
<p><b>Required FFmpeg Configuration:</b>
|
||||||
./configure --enable-shared --disable-static --enable-memalign-hack --enable-gpl --enable-swscaler</p>
|
./configure --enable-memalign-hack --enable-gpl --enable-swscale --enable-postproc
|
||||||
|
|
||||||
<p><b>Suggested Additional Options:</b>
|
<p><b>Suggested Additional Options:</b>
|
||||||
--disable-encoders --disable-muxers --enable-small</p>
|
--enable-w32threads --disable-encoders --disable-muxers --enable-small --enable-libfaad --disable-debug</p>
|
||||||
|
<p>
|
||||||
|
Note that --enable-w32threads is required for multithreaded decoding to work.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Changes</h2>
|
<h2>Changes</h2>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>1.17<ul>
|
||||||
|
<li>Updated FFmpeg to rev X</li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
<li>1.16<ul>
|
||||||
|
<li>Added many new and missing matroska codec ids</li>
|
||||||
|
<li>Added threads argument to set the number of decoding threads used</li>
|
||||||
|
<li>Completely removed FLAC cache</li>
|
||||||
|
<li>Updated FFmpeg to rev 12382</li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
<li>1.15<ul>
|
||||||
|
<li>Updated FFmpeg to rev 11518</li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
<li>1.14<ul>
|
<li>1.14<ul>
|
||||||
<li>If the output colorspace is YV12 or YUY2 the width and height may be automatically cropped by one pixel to make it an even number</li>
|
<li>If the output colorspace is YV12 or YUY2 the width and height may be automatically cropped by one pixel to make it an even number</li>
|
||||||
<li>FLAC cache is disabled because the static FLAC lib doesn't want to link</li>
|
<li>FLAC cache is disabled because the static FLAC lib doesn't want to link</li>
|
||||||
|
@ -172,8 +188,6 @@ tn:64:128:256
|
||||||
<li>Updated FFmpeg to rev 11413</li>
|
<li>Updated FFmpeg to rev 11413</li>
|
||||||
</ul></li>
|
</ul></li>
|
||||||
|
|
||||||
<h2>Changes</h2>
|
|
||||||
<ul>
|
|
||||||
<li>1.13<ul>
|
<li>1.13<ul>
|
||||||
<li>Now always sorts the output timecodes so native avc in mkv won't have out of order values</li>
|
<li>Now always sorts the output timecodes so native avc in mkv won't have out of order values</li>
|
||||||
<li>Fixed the missing '# timecode format v2' line in saved timecode files</li>
|
<li>Fixed the missing '# timecode format v2' line in saved timecode files</li>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -75,7 +75,7 @@ PVideoFrame FFPP::GetFrame(int n, IScriptEnvironment* Env) {
|
||||||
PVideoFrame Dst = Env->NewVideoFrame(vi);
|
PVideoFrame Dst = Env->NewVideoFrame(vi);
|
||||||
|
|
||||||
if (vi.IsYV12()) {
|
if (vi.IsYV12()) {
|
||||||
uint8_t *SrcData[3] = {(uint8_t *)Src->GetReadPtr(PLANAR_Y), (uint8_t *)Src->GetReadPtr(PLANAR_U), (uint8_t *)Src->GetReadPtr(PLANAR_V)};
|
const uint8_t *SrcData[3] = {(uint8_t *)Src->GetReadPtr(PLANAR_Y), (uint8_t *)Src->GetReadPtr(PLANAR_U), (uint8_t *)Src->GetReadPtr(PLANAR_V)};
|
||||||
int SrcStride[3] = {Src->GetPitch(PLANAR_Y), Src->GetPitch(PLANAR_U), Src->GetPitch(PLANAR_V)};
|
int SrcStride[3] = {Src->GetPitch(PLANAR_Y), Src->GetPitch(PLANAR_U), Src->GetPitch(PLANAR_V)};
|
||||||
uint8_t *DstData[3] = {Dst->GetWritePtr(PLANAR_Y), Dst->GetWritePtr(PLANAR_U), Dst->GetWritePtr(PLANAR_V)};
|
uint8_t *DstData[3] = {Dst->GetWritePtr(PLANAR_Y), Dst->GetWritePtr(PLANAR_U), Dst->GetWritePtr(PLANAR_V)};
|
||||||
int DstStride[3] = {Dst->GetPitch(PLANAR_Y), Dst->GetPitch(PLANAR_U), Dst->GetPitch(PLANAR_V)};
|
int DstStride[3] = {Dst->GetPitch(PLANAR_Y), Dst->GetPitch(PLANAR_U), Dst->GetPitch(PLANAR_V)};
|
||||||
|
@ -86,7 +86,7 @@ PVideoFrame FFPP::GetFrame(int n, IScriptEnvironment* Env) {
|
||||||
int SrcStride[1] = {Src->GetPitch()};
|
int SrcStride[1] = {Src->GetPitch()};
|
||||||
sws_scale(SWSTo422P, SrcData, SrcStride, 0, vi.height, InputPicture.data, InputPicture.linesize);
|
sws_scale(SWSTo422P, SrcData, SrcStride, 0, vi.height, InputPicture.data, InputPicture.linesize);
|
||||||
|
|
||||||
pp_postprocess(InputPicture.data, InputPicture.linesize, OutputPicture.data, OutputPicture.linesize, vi.width, vi.height, NULL, 0, PPMode, PPContext, 0);
|
pp_postprocess(const_cast<const uint8_t **>(InputPicture.data), InputPicture.linesize, OutputPicture.data, OutputPicture.linesize, vi.width, vi.height, NULL, 0, PPMode, PPContext, 0);
|
||||||
|
|
||||||
uint8_t *DstData[1] = {Dst->GetWritePtr()};
|
uint8_t *DstData[1] = {Dst->GetWritePtr()};
|
||||||
int DstStride[1] = {Dst->GetPitch()};
|
int DstStride[1] = {Dst->GetPitch()};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -64,9 +64,9 @@ AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnviro
|
||||||
bool VCache = Args[4].AsBool(true);
|
bool VCache = Args[4].AsBool(true);
|
||||||
const char *VCacheFile = Args[5].AsString("");
|
const char *VCacheFile = Args[5].AsString("");
|
||||||
const char *ACacheFile = Args[6].AsString("");
|
const char *ACacheFile = Args[6].AsString("");
|
||||||
int ACCompression = Args[7].AsInt(-1);
|
const char *PPString = Args[7].AsString("");
|
||||||
const char *PPString = Args[8].AsString("");
|
int PPQuality = Args[8].AsInt(PP_QUALITY_MAX);
|
||||||
int PPQuality = Args[9].AsInt(PP_QUALITY_MAX);
|
int Threads = Args[9].AsInt(1);
|
||||||
int SeekMode = Args[10].AsInt(1);
|
int SeekMode = Args[10].AsInt(1);
|
||||||
|
|
||||||
if (VTrack <= -2 && ATrack <= -2)
|
if (VTrack <= -2 && ATrack <= -2)
|
||||||
|
@ -75,13 +75,8 @@ AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnviro
|
||||||
if (SeekMode < -1 || SeekMode > 3)
|
if (SeekMode < -1 || SeekMode > 3)
|
||||||
Env->ThrowError("FFmpegSource: Invalid seekmode selected");
|
Env->ThrowError("FFmpegSource: Invalid seekmode selected");
|
||||||
|
|
||||||
#ifdef FLAC_CACHE
|
if (Threads < 1)
|
||||||
if (ACCompression < -1 || ACCompression > 8)
|
Env->ThrowError("FFmpegSource: Invalid thread count");
|
||||||
#else
|
|
||||||
if (ACCompression != -1)
|
|
||||||
#endif // FLAC_CACHE
|
|
||||||
Env->ThrowError("FFmpegSource: Invalid audio cache compression selected");
|
|
||||||
|
|
||||||
|
|
||||||
AVFormatContext *FormatContext;
|
AVFormatContext *FormatContext;
|
||||||
|
|
||||||
|
@ -93,12 +88,12 @@ AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnviro
|
||||||
FrameInfoVector Frames;
|
FrameInfoVector Frames;
|
||||||
|
|
||||||
if (IsMatroska) {
|
if (IsMatroska) {
|
||||||
return new FFMatroskaSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, ACCompression, PPString, PPQuality, Env, &Frames);
|
return new FFMatroskaSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, PPString, PPQuality, Threads, Env, &Frames);
|
||||||
} else {
|
} else {
|
||||||
// Do a separate indexing pass, enjoy the constructor sideeffects
|
// Do a separate indexing pass, enjoy the constructor sideeffects
|
||||||
if (SeekMode == -1)
|
if (SeekMode == -1)
|
||||||
delete new FFmpegSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, ACCompression, PPString, PPQuality, -2, Env, &Frames);
|
delete new FFmpegSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, PPString, PPQuality, Threads, -2, Env, &Frames);
|
||||||
return new FFmpegSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, ACCompression, PPString, PPQuality, SeekMode, Env, &Frames);
|
return new FFmpegSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, PPString, PPQuality, Threads, SeekMode, Env, &Frames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +133,7 @@ AVSValue __cdecl CreateFFPP(AVSValue Args, void* UserData, IScriptEnvironment* E
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) {
|
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) {
|
||||||
Env->AddFunction("FFmpegSource", "[source]s[vtrack]i[atrack]i[timecodes]s[vcache]b[vcachefile]s[acachefile]s[accompression]i[pp]s[ppquality]i[seekmode]i", CreateFFmpegSource, 0);
|
Env->AddFunction("FFmpegSource", "[source]s[vtrack]i[atrack]i[timecodes]s[vcache]b[vcachefile]s[acachefile]s[pp]s[ppquality]i[threads]i[seekmode]i", CreateFFmpegSource, 0);
|
||||||
Env->AddFunction("FFAudioSource", "[source]s[atrack]i[acachefile]s[ademuxedfile]s", CreateFFAudioSource, 0);
|
Env->AddFunction("FFAudioSource", "[source]s[atrack]i[acachefile]s[ademuxedfile]s", CreateFFAudioSource, 0);
|
||||||
Env->AddFunction("FFPP", "c[pp]s[ppquality]i", CreateFFPP, 0);
|
Env->AddFunction("FFPP", "c[pp]s[ppquality]i", CreateFFPP, 0);
|
||||||
return "FFmpegSource";
|
return "FFmpegSource";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -135,9 +135,9 @@ CodecID MatroskaToFFCodecID(TrackInfo *TI) {
|
||||||
case MAKEFOURCC('M', 'S', 'Z', 'H'):
|
case MAKEFOURCC('M', 'S', 'Z', 'H'):
|
||||||
return CODEC_ID_MSZH;
|
return CODEC_ID_MSZH;
|
||||||
case MAKEFOURCC('Z', 'L', 'I', 'B'):
|
case MAKEFOURCC('Z', 'L', 'I', 'B'):
|
||||||
return CODEC_ID_FLV1;
|
|
||||||
case MAKEFOURCC('F', 'L', 'V', '1'):
|
|
||||||
return CODEC_ID_ZLIB;
|
return CODEC_ID_ZLIB;
|
||||||
|
case MAKEFOURCC('F', 'L', 'V', '1'):
|
||||||
|
return CODEC_ID_FLV1;
|
||||||
/*
|
/*
|
||||||
case MAKEFOURCC('P', 'N', 'G', '1'):
|
case MAKEFOURCC('P', 'N', 'G', '1'):
|
||||||
return CODEC_ID_COREPNG;
|
return CODEC_ID_COREPNG;
|
||||||
|
@ -199,18 +199,33 @@ CodecID MatroskaToFFCodecID(TrackInfo *TI) {
|
||||||
default:
|
default:
|
||||||
return CODEC_ID_NONE;
|
return CODEC_ID_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (!strcmp(Codec, "V_MPEG4/ISO/AVC"))
|
} else if (!strcmp(Codec, "V_MPEG4/ISO/AVC"))
|
||||||
return CODEC_ID_H264;
|
return CODEC_ID_H264;
|
||||||
|
else if (!strcmp(Codec, "V_MPEG4/ISO/AP"))
|
||||||
|
return CODEC_ID_MPEG4;
|
||||||
else if (!strcmp(Codec, "V_MPEG4/ISO/ASP"))
|
else if (!strcmp(Codec, "V_MPEG4/ISO/ASP"))
|
||||||
return CODEC_ID_MPEG4;
|
return CODEC_ID_MPEG4;
|
||||||
|
else if (!strcmp(Codec, "V_MPEG4/ISO/SP"))
|
||||||
|
return CODEC_ID_MPEG4;
|
||||||
|
else if (!strcmp(Codec, "V_MPEG4/MS/V3"))
|
||||||
|
return CODEC_ID_MSMPEG4V3;
|
||||||
else if (!strcmp(Codec, "V_MPEG2"))
|
else if (!strcmp(Codec, "V_MPEG2"))
|
||||||
return CODEC_ID_MPEG2VIDEO;
|
return CODEC_ID_MPEG2VIDEO;
|
||||||
else if (!strcmp(Codec, "V_MPEG1"))
|
else if (!strcmp(Codec, "V_MPEG1"))
|
||||||
return CODEC_ID_MPEG2VIDEO; // still not a typo
|
return CODEC_ID_MPEG2VIDEO; // still not a typo
|
||||||
|
else if (!strcmp(Codec, "V_VC1"))
|
||||||
|
return CODEC_ID_VC1;
|
||||||
else if (!strcmp(Codec, "V_SNOW"))
|
else if (!strcmp(Codec, "V_SNOW"))
|
||||||
return CODEC_ID_SNOW;
|
return CODEC_ID_SNOW;
|
||||||
else if (!strcmp(Codec, "V_THEORA"))
|
else if (!strcmp(Codec, "V_THEORA"))
|
||||||
return CODEC_ID_THEORA;
|
return CODEC_ID_THEORA;
|
||||||
|
else if (!strcmp(Codec, "V_UNCOMPRESSED"))
|
||||||
|
return CODEC_ID_NONE; // bleh
|
||||||
|
else if (!strcmp(Codec, "V_QUICKTIME"))
|
||||||
|
return CODEC_ID_SVQ3;
|
||||||
|
else if (!strcmp(Codec, "V_CIPC"))
|
||||||
|
return CODEC_ID_NONE; // don't know, don't care
|
||||||
else if (!strncmp(Codec, "V_REAL/RV", 9)) {
|
else if (!strncmp(Codec, "V_REAL/RV", 9)) {
|
||||||
switch (Codec[9]) {
|
switch (Codec[9]) {
|
||||||
case '1':
|
case '1':
|
||||||
|
@ -227,6 +242,8 @@ CodecID MatroskaToFFCodecID(TrackInfo *TI) {
|
||||||
/* Audio Codecs */
|
/* Audio Codecs */
|
||||||
} else if (!strcmp(Codec, "A_AC3"))
|
} else if (!strcmp(Codec, "A_AC3"))
|
||||||
return CODEC_ID_AC3;
|
return CODEC_ID_AC3;
|
||||||
|
else if (!strcmp(Codec, "A_EAC3"))
|
||||||
|
return CODEC_ID_AC3;
|
||||||
else if (!strcmp(Codec, "A_MPEG/L3"))
|
else if (!strcmp(Codec, "A_MPEG/L3"))
|
||||||
return CODEC_ID_MP3;
|
return CODEC_ID_MP3;
|
||||||
else if (!strcmp(Codec, "A_MPEG/L2"))
|
else if (!strcmp(Codec, "A_MPEG/L2"))
|
||||||
|
@ -243,8 +260,20 @@ CodecID MatroskaToFFCodecID(TrackInfo *TI) {
|
||||||
case 32: return CODEC_ID_PCM_S32LE;
|
case 32: return CODEC_ID_PCM_S32LE;
|
||||||
default: return CODEC_ID_NONE;
|
default: return CODEC_ID_NONE;
|
||||||
}
|
}
|
||||||
|
} else if (!strcmp(Codec, "A_PCM/INT/BIG")) {
|
||||||
|
switch (TI->AV.Audio.BitDepth) {
|
||||||
|
case 8: return CODEC_ID_PCM_S8;
|
||||||
|
case 16: return CODEC_ID_PCM_S16BE;
|
||||||
|
case 24: return CODEC_ID_PCM_S24BE;
|
||||||
|
case 32: return CODEC_ID_PCM_S32BE;
|
||||||
|
default: return CODEC_ID_NONE;
|
||||||
|
}
|
||||||
} else if (!strcmp(Codec, "A_PCM/FLOAT/IEEE"))
|
} else if (!strcmp(Codec, "A_PCM/FLOAT/IEEE"))
|
||||||
return CODEC_ID_NONE; // no float codec id?
|
return CODEC_ID_NONE; // no float codec id?
|
||||||
|
else if (!strcmp(Codec, "A_FLAC"))
|
||||||
|
return CODEC_ID_FLAC;
|
||||||
|
else if (!strcmp(Codec, "A_MPC"))
|
||||||
|
return CODEC_ID_MUSEPACK8;
|
||||||
else if (!strcmp(Codec, "A_TTA1"))
|
else if (!strcmp(Codec, "A_TTA1"))
|
||||||
return CODEC_ID_TTA;
|
return CODEC_ID_TTA;
|
||||||
else if (!strcmp(Codec, "A_WAVPACK4"))
|
else if (!strcmp(Codec, "A_WAVPACK4"))
|
||||||
|
@ -263,6 +292,10 @@ CodecID MatroskaToFFCodecID(TrackInfo *TI) {
|
||||||
return CODEC_ID_ATRAC3;
|
return CODEC_ID_ATRAC3;
|
||||||
else if (!strncmp(Codec, "A_AAC", 5))
|
else if (!strncmp(Codec, "A_AAC", 5))
|
||||||
return CODEC_ID_AAC;
|
return CODEC_ID_AAC;
|
||||||
|
else if (!strcmp(Codec, "A_SPEEX"))
|
||||||
|
return CODEC_ID_SPEEX;
|
||||||
|
else if (!strcmp(Codec, "A_QUICKTIME"))
|
||||||
|
return CODEC_ID_NONE; // no
|
||||||
else if (!strcmp(Codec, "A_MS/ACM")) {
|
else if (!strcmp(Codec, "A_MS/ACM")) {
|
||||||
// nothing useful here anyway?
|
// nothing useful here anyway?
|
||||||
//#include "Mmreg.h"
|
//#include "Mmreg.h"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2007 Fredrik Mellbin
|
// Copyright (c) 2007-2008 Fredrik Mellbin
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
Loading…
Reference in a new issue