Author: toff
Date: 2004-10-27 00:44:54 +0400 (Wed, 27 Oct 2004)
New Revision: 924
Added:
tags/foo_matroska_0.8/
tags/foo_matroska_0.8/DbgOut.h
tags/foo_matroska_0.8/foo_input_matroska.cpp
tags/foo_matroska_0.8/matroska_parser.cpp
tags/foo_matroska_0.8/matroska_parser.h
Removed:
tags/foo_matroska_0.8/DbgOut.h
tags/foo_matroska_0.8/foo_input_matroska.cpp
tags/foo_matroska_0.8/matroska_parser.cpp
tags/foo_matroska_0.8/matroska_parser.h
Modified:
tags/foo_matroska_0.8/foo_matroska.dsp
Log:
before new new tag
Copied: tags/foo_matroska_0.8 (from rev 688, trunk/foo_matroska)
Deleted: tags/foo_matroska_0.8/DbgOut.h
Copied: tags/foo_matroska_0.8/DbgOut.h (from rev 904, trunk/foo_matroska/DbgOut.h)
Deleted: tags/foo_matroska_0.8/foo_input_matroska.cpp
Copied: tags/foo_matroska_0.8/foo_input_matroska.cpp (from rev 904, trunk/foo_matroska/foo_input_matroska.cpp)
===================================================================
--- trunk/foo_matroska/foo_input_matroska.cpp 2004-10-22 19:49:54 UTC (rev 904)
+++ tags/foo_matroska_0.8/foo_input_matroska.cpp 2004-10-26 20:44:54 UTC (rev 924)
@@ -0,0 +1,535 @@
+/*
+ * Part of the foobar2000 Matroska plugin
+ *
+ * Copyright (C) Jory Stone (jcsston at toughguy net) - 2003-2004
+ *
+ * Based on mp4_parser.cpp from foo_std_input
+ * Copyright (c) 2001-2003, Peter Pawlowski
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*!
+ \file foo_input_matroska.cpp
+ \version $Id$
+ \brief A foobar2000 plugin for Matroska
+ \author Jory Stone <jcsston @ toughguy.net>
+*/
+
+//Memory Leak Debuging define
+#ifdef _DEBUG
+//#define _CRTDBG_MAP_ALLOC
+#include <stdlib.h>
+#include <crtdbg.h>
+
+// Debug Memory Alloc hook
+int YourAllocHook(int allocType, void *userData, size_t size, int blockType, long requestNumber, const unsigned char *filename, int lineNumber)
+{
+ /*static int64 totalSize;
+ if (allocType == 1)
+ totalSize += size;
+ else if (allocType == 3)
+ totalSize -= _msize(userData);
+ else if (allocType == 500)
+ totalSize = 0;
+ else if (allocType == 1000)
+ *(int64 *)userData = totalSize;*/
+ //I can use this to find exactly where a leak started
+ if (size == 136)
+ {
+ //NOTE(_T("Memory Alloc Hook tripped"));
+ return 1;
+ }
+ return 1;
+};
+#endif
+
+#include "../SDK/foobar2000.h"
+
+#include <commctrl.h>
+#include <Shlwapi.h>
+#include <time.h>
+#include "resource.h"
+#include "matroska_parser.h"
+#include "DbgOut.h"
+
+class input_matroska : public input
+{
+ MatroskaAudioParser *m_parser;
+ reader *m_matroskaFile;
+ packet_decoder *m_decoder;
+
+ int m_TrackNo;
+ // Compressed data frame
+ MatroskaAudioFrame * m_frame;
+ unsigned m_frame_remaining;
+
+ unsigned m_skip_samples;
+ unsigned m_skip_frames;
+ unsigned m_expected_sample_rate;
+ unsigned m_expected_channels;
+ uint64 m_timescale;
+
+ // VBR bitrate display stuff
+ double m_vbr_last_duration;
+ unsigned m_vbr_update_frames,m_vbr_update_bytes;
+ double m_vbr_update_time;
+ bool m_vbr_update_enabled;
+ unsigned m_vbr_update_interval;
+ uint64 m_length,m_position;
+
+ // A temp decoding buffer
+ audio_chunk_i m_tempchunk;
+
+public:
+ input_matroska()
+ {
+ m_parser = NULL;
+ m_decoder = NULL;
+ m_TrackNo = 0;
+ m_skip_samples = 0;
+ m_skip_frames = 0;
+ m_timescale = TIMECODE_SCALE;
+ m_frame = 0;
+ m_frame_remaining = 0;
+
+ m_vbr_last_duration = 0;
+ m_vbr_update_frames = 0;
+ m_vbr_update_bytes = 0;
+ m_vbr_update_time = 0;
+ m_vbr_update_enabled = true;
+ m_vbr_update_interval = 32;
+ m_position = 0;
+ m_length = 0;
+
+ }
+
+ ~input_matroska()
+ {
+ cleanup();
+ }
+
+ virtual bool test_filename(const char * fn,const char * ext)
+ {
+ return (!stricmp(ext,"MKA") || !stricmp(ext,"MKV"));
+ }
+
+ virtual bool open(reader *r, file_info *info, unsigned flags)
+ {
+ // TODO : Use the Cueing Data if we can seek
+
+ /*if (!r->can_seek())
+ {
+ console::error("Matroska: unseekable streams not supported.");
+ return false;
+ }*/
+
+ cleanup();
+
+ m_parser = new MatroskaAudioParser(r);
+
+ if (m_parser->Parse(!(flags & OPEN_FLAG_DECODE))) {
+ console::error("Matroska: Invalid Matroska file.");
+ return false;
+ }
+
+ m_parser->SetSubSong(info->get_subsong_index());
+
+ m_TrackNo = m_parser->GetFirstAudioTrack();
+ if (m_TrackNo == -1) {
+ console::error("Matroska: no decodable streams found.");
+ return false;
+ }
+ m_parser->SetCurrentTrack(m_TrackNo);
+
+
+ // Set the current track
+
+ MatroskaTrackInfo ¤tTrack = m_parser->GetTrack(m_TrackNo);
+ m_expected_channels = currentTrack.channels;
+ m_expected_sample_rate = currentTrack.samplesOutputPerSec == 0 ? currentTrack.samplesPerSec : currentTrack.samplesOutputPerSec;
+
+ info->info_set_int("channels", m_expected_channels);
+ info->info_set_int("samplerate", m_expected_sample_rate);
+// info->info_set_int("bitspersample", currentTrack.bitsPerSample);
+
+ {
+ packet_decoder::matroska_setup setup;
+ string8 codec_temp(currentTrack.codecID.c_str());
+ setup.codec_id = codec_temp;
+ setup.sample_rate = currentTrack.samplesPerSec;
+ setup.sample_rate_output = currentTrack.samplesOutputPerSec;
+ setup.channels = m_expected_channels;
+ setup.codec_private_size = currentTrack.codecPrivate.size();
+ setup.codec_private = ¤tTrack.codecPrivate[0];
+
+ m_decoder = packet_decoder::g_open(packet_decoder::owner_matroska,0,&setup,sizeof(setup),info);
+
+ if (m_decoder == NULL)
+ {
+ console::error(uStringPrintf("Matroska: unable to find a \"%s\" packet decoder object.", (const char*)codec_temp));
+ cleanup();
+ return false;
+ } else {
+ console::info(uStringPrintf("Matroska: using '%s' decoder.", (const char*)m_decoder->get_name()));
+ }
+ }
+
+ m_expected_channels = info->info_get_int("channels");
+ m_expected_sample_rate = info->info_get_int("samplerate");
+
+ m_decoder->set_stream_property(packet_decoder::property_channels,m_expected_channels,0,0,info);
+ m_decoder->set_stream_property(packet_decoder::property_samplerate,m_expected_sample_rate,0,0,info);
+ m_decoder->set_stream_property(packet_decoder::property_bitspersample,currentTrack.bitsPerSample,0,0,info);
+
+ m_parser->SetFB2KInfo(info);
+ // The timecode scale in Matroska is in milliseconds, but foobar deals in seconds
+ m_timescale = m_parser->GetTimecodeScale() * 1000;
+
+ m_length = duration_to_samples(m_parser->GetDuration());
+ m_position = 0;
+
+ info->set_length((double)(int64)m_parser->GetDuration() / 1000000000.0);
+
+ info->info_set_bitrate(m_parser->GetAvgBitrate());
+
+
+ if (flags & OPEN_FLAG_DECODE)
+ {
+ m_skip_samples = 0;
+ m_skip_frames = 0;
+ m_frame_remaining = 0;
+ m_frame = 0;
+
+ if (!seek(0)) return false;
+ }
+
+ return true;
+ }
+
+ virtual int run(audio_chunk * chunk)
+ {
+ if (m_decoder == NULL)
+ {
+ console::error("Matroska: attempting to decode without a loaded decoder.");
+ return -1;
+ }
+
+ if (m_position>=m_length)
+ {
+ return 0;
+ }
+
+ bool done = false;
+
+ do
+ {
+ unsigned char *buffer = NULL;
+ unsigned int buffer_size = 0;
+ bool skip_this_frame = false;
+/*
+ {
+ int64 delta = duration_to_samples(m_frame.timecode) - m_position;
+ console::info(uStringPrintf("drift: %d",(int)delta));
+ }
+*/
+
+ if (m_frame_remaining==0 || m_frame==0)
+ {
+ if (m_frame!=0)
+ delete m_frame;
+ m_frame = m_parser->ReadSingleFrame();
+ if (m_frame==0)
+ return 0;
+ m_frame_remaining = m_frame->dataBuffer.size();
+ if (m_frame_remaining == 0)
+ return 0;
+ }
+
+ {
+ unsigned ptr = m_frame->dataBuffer.size() - (m_frame_remaining--);
+ buffer = (unsigned char*)&m_frame->dataBuffer.at(ptr)[0];
+ buffer_size = m_frame->dataBuffer.at(ptr).size();
+ }
+
+ m_tempchunk.reset();
+ if (!m_decoder->decode(buffer,buffer_size,&m_tempchunk))
+ {
+ console::error(uStringPrintf("Matroska: '%s' decode error.", (const char*)m_decoder->get_name()));
+ console::error(uStringPrintf("buffer_size = %d, m_frame_remaining = %d", buffer_size, m_frame_remaining));
+ console::error(uStringPrintf("buffer 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]));
+ cleanup();
+ return -1;
+ }
+ else if (m_tempchunk.is_valid())
+ {
+ m_vbr_update_frames++;
+ m_vbr_update_bytes += buffer_size;
+ m_vbr_update_time += (m_vbr_last_duration = m_tempchunk.get_duration());
+ }
+
+ // TODO : maybe we could not decode the skipped frame to get faster seeking ???
+
+ if (m_skip_frames>0)
+ {
+ skip_this_frame = true;
+ m_skip_frames--;
+ }
+
+
+ if (!skip_this_frame)
+ {
+ unsigned offset = (unsigned)duration_to_samples(0);//m_frame.timecode);
+ unsigned duration = (unsigned)duration_to_samples(m_frame->duration);
+ // console::info(uStringPrintf("duration: %u, offset: %u",duration,offset));
+
+ if (m_tempchunk.is_empty())
+ {
+ if (duration > 0)
+ {
+ m_tempchunk.set_srate(m_expected_sample_rate);
+ m_tempchunk.set_channels(m_expected_channels);
+ m_tempchunk.pad_with_silence(duration);
+#ifdef _DEBUG
+ console::warning("Matroska: decoder returned empty chunk from a non-empty Matroska frame.");
+#endif
+ }
+ }
+ else if (!m_tempchunk.is_valid())
+ {
+ console::error("Matroska: decoder produced invalid chunk.");
+ cleanup();
+ return -1;
+ }
+ unsigned samplerate,channels,decoded_sample_count;
+
+ samplerate = m_tempchunk.get_srate();
+ channels = m_tempchunk.get_channels();
+ decoded_sample_count = m_tempchunk.get_sample_count();
+
+ // !!!!!!!!!!!!!!! HACK ALERT !!!!!!!!!!!!!!!!!!!!!!!!
+ duration = decoded_sample_count;
+ // !!!!!!!!!!!!!!! HACK ALERT !!!!!!!!!!!!!!!!!!!!!!!!
+
+ if (decoded_sample_count < duration)
+ {
+#ifdef _DEBUG
+ console::warning("Matroska: decoded frame smaller than expected.");
+#endif
+ decoded_sample_count = duration;
+ m_tempchunk.pad_with_silence(decoded_sample_count);
+ }
+
+ if (duration < offset)
+ duration = 0;
+ else
+ duration -= offset;
+
+ if (m_skip_samples>0)
+ {
+ unsigned int delta = (unsigned)m_skip_samples;
+ if (delta > duration) delta = duration;
+ offset += delta;
+ duration -= delta;
+ m_skip_samples -= delta;
+ }
+
+ {
+ uint64 max = m_length - m_position;
+ if (duration>max)
+ duration = max;
+ m_position += duration;
+ if (m_position==m_length && duration==0)
+ return 0;
+ }
+
+
+ if (duration > 0)
+ {
+ chunk->set_data_64(m_tempchunk.get_data() + offset * channels,
+ duration,channels,samplerate);
+ done = true;
+ }
+ }
+ }
+ while(!done);
+
+ return done ? 1 : 0;
+ }
+
+ virtual set_info_t set_info(reader *r, const file_info * info)
+ {
+ NOTE2("input_matroska::set_info(r = 0x%p, info = 0x%p)", r, info);
+ unsigned rv = 1;
+
+ // We have to create a new parser
+ MatroskaAudioParser writeReader(r);
+ writeReader.Parse(true);
+
+ int TrackNo = writeReader.GetFirstAudioTrack();
+ if (TrackNo != -1) {
+ // Set the current track
+ writeReader.SetCurrentTrack(m_TrackNo);
+ writeReader.SetSubSong(info->get_subsong_index());
+
+ rv = writeReader.WriteTags(info);
+ }
+
+ return !rv ? SET_INFO_SUCCESS : SET_INFO_FAILURE;
+ }
+
+ virtual bool seek(double seconds)
+ {
+ NOTE1("input_matroska::seek(seconds = %f)", seconds);
+
+ if (m_decoder == NULL)
+ {
+ console::error("Matroska: attempting to seek while not open.");
+ return false;
+ }
+
+
+ {
+ double srate = (double)m_expected_sample_rate;
+ seconds = floor(seconds * srate + 0.5) / srate;
+ }
+
+ double max_frame_dependency_time = m_decoder->get_max_frame_dependency_time();
+ if (max_frame_dependency_time>seconds) max_frame_dependency_time = seconds;
+ unsigned max_frame_dependency_samples = (unsigned)(m_expected_sample_rate * max_frame_dependency_time + 0.5);
+
+ unsigned frames_to_skip = 0;
+
+ m_position = (uint64)(m_expected_sample_rate * seconds + 0.5);
+
+ double time_to_skip = 0;
+ if (!m_parser->Seek(seconds-max_frame_dependency_time,frames_to_skip,time_to_skip,m_expected_sample_rate)) return false;
+
+ m_skip_frames = frames_to_skip;
+
+ m_skip_samples = (unsigned)(time_to_skip * m_expected_sample_rate + 0.5) + max_frame_dependency_samples;
+ m_frame_remaining = 0;
+
+ //console::info(uStringPrintf("skip samples: %u",m_skip_samples));
+
+ return true;
+ }
+
+ virtual bool is_our_content_type(const char *url, const char *type)
+ {
+ NOTE2("input_matroska::is_our_content_type(url = %s, type = %s)", url, type);
+
+ return !stricmp(type, "audio/x-matroska")
+ || !stricmp(type, "video/x-matroska")
+ // Just to be safe check for ones without x-
+ // However, You're supposed to use x-* unless it's offically registered
+ || !stricmp(type, "audio/matroska")
+ || !stricmp(type, "video/matroska");
+ }
+
+ virtual bool get_dynamic_info(file_info * out,double * timestamp_delta,bool * b_track_change)
+ {
+ bool ret = false;
+ if (m_vbr_update_enabled)
+ {
+ if (m_vbr_update_time > 0 && m_vbr_update_frames >= m_vbr_update_interval)
+ {
+ int val = (int) ( ((double)m_vbr_update_bytes * 8.0 / m_vbr_update_time + 500.0) / 1000.0 );
+ if (val != out->info_get_bitrate_vbr())
+ {
+ *timestamp_delta = - (m_vbr_update_time - m_vbr_last_duration); //relative to last frame beginning;
+ out->info_set_bitrate_vbr(val);
+ ret = true;
+ }
+ m_vbr_update_frames = 0; m_vbr_update_bytes = 0;
+ m_vbr_update_time = 0;
+ }
+ }
+ return ret;
+ }
+
+protected:
+ void cleanup()
+ {
+ if (m_decoder != NULL)
+ {
+ m_decoder->service_release();
+ m_decoder = NULL;
+ }
+ if (m_frame)
+ {
+ delete m_frame;
+ m_frame = 0;
+ }
+ if (m_parser != NULL) {
+ delete m_parser;
+ m_parser = NULL;
+ }
+ }
+ int64 duration_to_samples(int64 val)
+ {
+ return (int64) ( (double)val * (double)m_expected_sample_rate / 1000000000.0 + 0.5);
+ }
+
+ int64 samples_to_duration(int64 val)
+ {
+ return (int64) ( (double)val * 1000000000.0 / (double)m_expected_sample_rate + 0.5);
+ }
+
+};
+
+static service_factory_t<input, input_matroska> foo_matroska;
+
+class track_indexer_matroska : public track_indexer
+{
+public:
+ virtual int get_tracks(const char* filename, callback * out, reader * r)
+ {
+ NOTE3("track_indexer_matroska::get_tracks(filename = %s, out = 0x%p, r = 0x%p", filename, out, r);
+ // Let's check the filename
+ if (!stricmp_utf8_partial(filename, "http://", 7)) {
+ // HTTP streams and chapters togther are not supported, requires seeking
+ return 0;
+ } else if (strlen(filename) > 3) {
+ const char *ext = filename+strlen(filename)-3;
+ if (!!stricmp(ext, "MKA") && !!stricmp(ext, "MKV"))
+ return 0;
+ } else {
+ return 0;
+ }
+ if (r == NULL)
+ r = file::g_open_read(filename);
+ if (r == NULL) return 0;
+
+ MatroskaAudioParser scanner(r);
+ scanner.Parse(true);
+
+ std::vector<MatroskaChapterInfo> &chapters = scanner.GetChapters();
+
+ if (chapters.size() > 0) {
+ for (size_t c = 0; c < chapters.size(); c++) {
+ MatroskaChapterInfo ¤tChapter = chapters.at(c);
+ out->on_entry(make_playable_location(filename, (int)c));
+ }
+ r->reader_release();
+ return 1;
+ }
+
+ r->reader_release();
+ return 0;
+ }
+};
+
+static service_factory_single_t<track_indexer,track_indexer_matroska> foo_matroska1;
+
+DECLARE_COMPONENT_VERSION("Matroska Plugin" ,"0.8.0","Copyright (C) 2003-2004 Jory Stone ([email protected])\nCopyright (C) 2003-2004 Peter Pawlowski");
+
+
+DECLARE_FILE_TYPE("Matroska Audio files","*.MKA");
Modified: tags/foo_matroska_0.8/foo_matroska.dsp
===================================================================
--- trunk/foo_matroska/foo_matroska.dsp 2004-07-29 23:05:28 UTC (rev 688)
+++ tags/foo_matroska_0.8/foo_matroska.dsp 2004-10-26 20:44:54 UTC (rev 924)
@@ -43,7 +43,7 @@
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FOO_MATROSKA_EXPORTS" /YX /FD /c
-# ADD CPP /nologo /MD /W3 /GX /O1 /I "../../libebml" /I "../../libmatroska" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FOO_MATROSKA_EXPORTS" /YX /FD /c
+# ADD CPP /nologo /MD /W3 /GX /O1 /I "libebml" /I "libmatroska" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FOO_MATROSKA_EXPORTS" /FAcs /FR /YX /FD /c
# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
# ADD BASE RSC /l 0x409 /d "NDEBUG"
@@ -53,7 +53,8 @@
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386
-# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"F:\foobar2000\components\foo_matroska.dll"
+# SUBTRACT LINK32 /debug
!ELSEIF "$(CFG)" == "foo_matroska - Win32 Debug"
@@ -69,7 +70,7 @@
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FOO_MATROSKA_EXPORTS" /YX /FD /GZ /c
-# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../../libebml" /I "../../libmatroska" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FOO_MATROSKA_EXPORTS" /YX /FD /GZ /c
+# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "libebml" /I "libmatroska" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FOO_MATROSKA_EXPORTS" /FAcs /FR /YX /FD /GZ /c
# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD BASE RSC /l 0x409 /d "_DEBUG"
@@ -79,7 +80,7 @@
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
-# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /dll /map /debug /machine:I386 /out:"F:\foobar2000\components\foo_matroska.dll" /pdbtype:sept
!ENDIF
Deleted: tags/foo_matroska_0.8/matroska_parser.cpp
Copied: tags/foo_matroska_0.8/matroska_parser.cpp (from rev 709, trunk/foo_matroska/matroska_parser.cpp)
===================================================================
--- trunk/foo_matroska/matroska_parser.cpp 2004-08-06 18:14:55 UTC (rev 709)
+++ tags/foo_matroska_0.8/matroska_parser.cpp 2004-10-26 20:44:54 UTC (rev 924)
@@ -0,0 +1,2340 @@
+/*
+ * Part of the foobar2000 Matroska plugin
+ *
+ * Copyright (C) Jory 'jcsston' Stone (jcsston at toughguy net) - 2003-2004
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*!
+ \file matroska_parser.cpp
+ \version $Id$
+ \brief An audio slated Matroska Reader+Parser
+ \author Jory Stone <jcsston @ toughguy.net>
+*/
+
+#include "matroska_parser.h"
+
+#include <string>
+using std::string;
+
+using namespace LIBEBML_NAMESPACE;
+using namespace LIBMATROSKA_NAMESPACE;
+
+static const char * rg_fields[]=
+{
+ "REPLAYGAIN_GAIN",
+ "REPLAYGAIN_PEAK"
+};
+
+static const char * rg_track_fields[]=
+{
+ "REPLAYGAIN_TRACK_GAIN",
+ "REPLAYGAIN_TRACK_PEAK"
+};
+
+static const char * rg_album_fields[]=
+{
+ "REPLAYGAIN_ALBUM_GAIN",
+ "REPLAYGAIN_ALBUM_PEAK"
+};
+
+#define MATROSKA_TAG_IDX 0
+#define FOOBAR2K_TAG_IDX 1
+
+static const char * chapter_tag_mapping[][2] =
+{
+ { "PART_NUMBER", "TRACKNUMBER" }
+};
+
+static const char * edition_tag_mapping[][2] =
+{
+ { "TITLE", "ALBUM" },
+ { "SUBTITLE", "SUBALBUM"}
+};
+
+static const char * hidden_edition_field[] =
+{
+ "CATALOG_NUMBER",
+ "TOTAL_PARTS"
+};
+
+static const char * hidden_chapter_field[] =
+{
+ "CD_TRACK_FLAGS",
+};
+
+bool starts_with(const string &s, const char *start) {
+ return strncmp(s.c_str(), start, strlen(start)) == 0;
+}
+
+uint64 MatroskaAudioParser::SecondsToTimecode(double seconds)
+{
+ return (uint64)floor(seconds * 1000000000);
+}
+
+double MatroskaAudioParser::TimecodeToSeconds(uint64 code,unsigned samplerate_hint)
+{
+ return ((double)(int64)code / 1000000000);
+}
+
+
+MatroskaAudioFrame::MatroskaAudioFrame()
+{
+ timecode = 0;
+ duration = 0;
+};
+
+void MatroskaAudioFrame::Reset()
+{
+ timecode = 0;
+ duration = 0;
+};
+
+MatroskaSimpleTag::MatroskaSimpleTag()
+{
+ name = L"";
+ value = L"";
+ language = "und";
+ defaultFlag = 1;
+ hidden = false;
+ removalPending = false;
+};
+
+MatroskaTagInfo::MatroskaTagInfo()
+{
+ targetTrackUID = 0;
+ targetEditionUID = 0;
+ targetChapterUID = 0;
+ targetAttachmentUID = 0;
+ targetTypeValue = 0;
+};
+
+void MatroskaTagInfo::SetTagValue(const char *name, const char *value, int index)
+{
+ for (size_t s = 0; s < tags.size(); s++)
+ {
+ MatroskaSimpleTag ¤tSimpleTag = tags.at(s);
+ if (strcmpi(currentSimpleTag.name.GetUTF8().c_str(), name) == 0)
+ {
+ if(index == 0)
+ {
+ currentSimpleTag.value.SetUTF8(value);
+ currentSimpleTag.removalPending = false;
+ return;
+ }
+ index--;
+ }
+ }
+
+ // If we are here then we didn't find this tag in the vector already
+ MatroskaSimpleTag newSimpleTag;
+ newSimpleTag.name.SetUTF8(name);
+ newSimpleTag.value.SetUTF8(value);
+ newSimpleTag.removalPending = false;
+ tags.push_back(newSimpleTag);
+};
+
+void MatroskaTagInfo::RemoveMarkedTags()
+{
+ for (int i = tags.size()-1; i >= 0; i--)
+ {
+ MatroskaSimpleTag &simpleTag = tags.at(i);
+ if(simpleTag.removalPending == true)
+ {
+ tags.erase(tags.begin() + i);
+ }
+ }
+}
+
+void MatroskaTagInfo::MarkAllAsRemovalPending()
+{
+ for (int i = tags.size()-1; i >= 0; i--)
+ {
+ MatroskaSimpleTag &simpleTag = tags.at(i);
+ simpleTag.removalPending = true;
+ }
+}
+
+MatroskaChapterDisplayInfo::MatroskaChapterDisplayInfo() {
+ string = L"";
+};
+
+MatroskaChapterInfo::MatroskaChapterInfo() {
+ chapterUID = 0;
+ timeStart = 0;
+ timeEnd = 0;
+}
+
+MatroskaEditionInfo::MatroskaEditionInfo() {
+ editionUID = 0;
+}
+
+MatroskaTrackInfo::MatroskaTrackInfo() {
+ trackNumber = 0;
+ trackUID = 0;
+ duration = 0;
+
+ channels = 1;
+ samplesPerSec = 0;
+ samplesOutputPerSec = 0;
+ bitsPerSample = 0;
+ avgBytesPerSec = 0;
+ defaultDuration = 0;
+
+ name = L"";
+
+ codecPrivateReady = false;
+};
+
+MatroskaAudioParser::MatroskaAudioParser(reader *input)
+ : m_IOCallback(input),
+ m_InputStream(m_IOCallback)
+{
+ m_TimecodeScale = TIMECODE_SCALE;
+ m_FileDate = 0;
+ m_Duration = 0;
+ m_CurrentTimecode = 0;
+ m_ElementLevel0 = NULL;
+ //UpperElementLevel = 0;
+ m_CurrentEdition = 0;
+ m_CurrentChapter = 0;
+ m_FileSize = input->get_length();
+ m_TagPos = 0;
+ m_TagSize = 0;
+ m_TagScanRange = 1024 * 64;
+ m_CurrentTrackNo = 0;
+};
+
+MatroskaAudioParser::~MatroskaAudioParser() {
+ if (m_ElementLevel0 != NULL)
+ delete m_ElementLevel0;
+
+ while (!m_Queue.empty()) {
+ MatroskaAudioFrame *currentPacket = m_Queue.front();
+ delete currentPacket;
+ m_Queue.pop();
+ }
+};
+
+int MatroskaAudioParser::Parse(bool bInfoOnly, bool bBreakAtClusters)
+{
+ try {
+ int UpperElementLevel = 0;
+ bool bAllowDummy = false;
+ // Elements for different levels
+ EbmlElement *ElementLevel1 = NULL;
+ EbmlElement *ElementLevel2 = NULL;
+ EbmlElement *ElementLevel3 = NULL;
+ EbmlElement *ElementLevel4 = NULL;
+ EbmlElement *ElementLevel5 = NULL;
+
+ // Be sure we are at the beginning of the file
+ m_IOCallback.setFilePointer(0);
+ // Find the EbmlHead element. Must be the first one.
+ m_ElementLevel0 = m_InputStream.FindNextID(EbmlHead::ClassInfos, 0xFFFFFFFFFFFFFFFFL);
+ if (m_ElementLevel0 == NULL) {
+ return 1;
+ }
+ //We must have found the EBML head :)
+ m_ElementLevel0->SkipData(m_InputStream, m_ElementLevel0->Generic().Context);
+ delete m_ElementLevel0;
+
+ // Next element must be a segment
+ m_ElementLevel0 = m_InputStream.FindNextID(KaxSegment::ClassInfos, 0xFFFFFFFFFFFFFFFFL);
+ if (m_ElementLevel0 == NULL) {
+ //No segment/level 0 element found.
+ return 1;
+ }
+ if (!(EbmlId(*m_ElementLevel0) == KaxSegment::ClassInfos.GlobalId)) {
+ delete m_ElementLevel0;
+ m_ElementLevel0 = NULL;
+ return 1;
+ }
+
+ UpperElementLevel = 0;
+ // We've got our segment, so let's find the tracks
+ ElementLevel1 = m_InputStream.FindNextElement(m_ElementLevel0->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, true, 1);
+ while (ElementLevel1 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+
+ if (EbmlId(*ElementLevel1) == KaxSeekHead::ClassInfos.GlobalId) {
+ if (m_IOCallback.seekable()) {
+ Parse_MetaSeek(ElementLevel1, bInfoOnly);
+ if (m_TagPos == 0) {
+ // Search for them at the end of the file
+ if (m_TagScanRange > 0)
+ {
+ m_IOCallback.setFilePointer(m_FileSize - m_TagScanRange);
+ binary Buffer[4];
+ while (m_IOCallback.read(Buffer, 3) >= 3)
+ {//0x18
+ if ((Buffer[0] == 0x54) && (Buffer[1] == 0xc3) && (Buffer[2] == 0x67))
+ {
+ uint64 startPos = m_IOCallback.getFilePointer();
+
+ //seek back 3 bytes, so libmatroska can find the Tags element Ebml ID
+ m_IOCallback.setFilePointer(-4, seek_current);
+
+ EbmlElement *levelUnknown = m_InputStream.FindNextID(KaxTags::ClassInfos, 0xFFFFFFFFFFFFFFFFL);
+ if ((levelUnknown != NULL)
+ && (m_FileSize >= startPos + levelUnknown->GetSize())
+ && (EbmlId(*levelUnknown) == KaxTags::ClassInfos.GlobalId))
+ {
+ Parse_Tags(static_cast<KaxTags *>(levelUnknown));
+ delete levelUnknown;
+ levelUnknown = NULL;
+ break;
+ }
+ delete levelUnknown;
+ levelUnknown = NULL;
+
+ //Restore the file pos
+ m_IOCallback.setFilePointer(startPos);
+ }
+ //seek back 2 bytes
+ m_IOCallback.setFilePointer(-2, seek_current);
+ }
+ } else {
+ //m_TagPos = m_FileSize;
+ }
+ }
+ }
+ }else if (EbmlId(*ElementLevel1) == KaxInfo::ClassInfos.GlobalId) {
+ // General info about this Matroska file
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, bAllowDummy);
+ while (ElementLevel2 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+
+ if (EbmlId(*ElementLevel2) == KaxTimecodeScale::ClassInfos.GlobalId) {
+ KaxTimecodeScale &TimeScale = *static_cast<KaxTimecodeScale *>(ElementLevel2);
+ TimeScale.ReadData(m_InputStream.I_O());
+
+ //matroskaGlobalTrack->SetTimecodeScale(uint64(TimeScale));
+ m_TimecodeScale = uint64(TimeScale);
+ } else if (EbmlId(*ElementLevel2) == KaxDuration::ClassInfos.GlobalId) {
+ KaxDuration &duration = *static_cast<KaxDuration *>(ElementLevel2);
+ duration.ReadData(m_InputStream.I_O());
+
+ // it's in milliseconds?
+ m_Duration = double(duration) * (double)(int64)m_TimecodeScale;
+ //matroskaGlobalTrack->SetDuration(float(duration) * int64(m_TimecodeScale) / 1000000000.0);
+
+ } else if (EbmlId(*ElementLevel2) == KaxDateUTC::ClassInfos.GlobalId) {
+ KaxDateUTC & DateUTC = *static_cast<KaxDateUTC *>(ElementLevel2);
+ DateUTC.ReadData(m_InputStream.I_O());
+
+ m_FileDate = DateUTC.GetEpochDate();
+
+ } else if (EbmlId(*ElementLevel2) == KaxSegmentFilename::ClassInfos.GlobalId) {
+ KaxSegmentFilename &tag_SegmentFilename = *static_cast<KaxSegmentFilename *>(ElementLevel2);
+ tag_SegmentFilename.ReadData(m_InputStream.I_O());
+
+ m_SegmentFilename = *static_cast<EbmlUnicodeString *>(&tag_SegmentFilename);
+
+ } else if (EbmlId(*ElementLevel2) == KaxMuxingApp::ClassInfos.GlobalId) {
+ KaxMuxingApp &tag_MuxingApp = *static_cast<KaxMuxingApp *>(ElementLevel2);
+ tag_MuxingApp.ReadData(m_InputStream.I_O());
+
+ m_MuxingApp = *static_cast<EbmlUnicodeString *>(&tag_MuxingApp);
+
+ } else if (EbmlId(*ElementLevel2) == KaxWritingApp::ClassInfos.GlobalId) {
+ KaxWritingApp &tag_WritingApp = *static_cast<KaxWritingApp *>(ElementLevel2);
+ tag_WritingApp.ReadData(m_InputStream.I_O());
+
+ m_WritingApp = *static_cast<EbmlUnicodeString *>(&tag_WritingApp);
+
+ } else if (EbmlId(*ElementLevel2) == KaxTitle::ClassInfos.GlobalId) {
+ KaxTitle &Title = *static_cast<KaxTitle*>(ElementLevel2);
+ Title.ReadData(m_InputStream.I_O());
+ m_FileTitle = UTFstring(Title).c_str();
+ }
+
+ if (UpperElementLevel > 0) { // we're coming from ElementLevel3
+ UpperElementLevel--;
+ delete ElementLevel2;
+ ElementLevel2 = ElementLevel3;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel2->SkipData(m_InputStream, ElementLevel2->Generic().Context);
+ delete ElementLevel2;
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, bAllowDummy);
+ }
+ }
+ }else if (EbmlId(*ElementLevel1) == KaxChapters::ClassInfos.GlobalId) {
+ Parse_Chapters(static_cast<KaxChapters *>(ElementLevel1));
+
+ }else if (EbmlId(*ElementLevel1) == KaxTags::ClassInfos.GlobalId) {
+ Parse_Tags(static_cast<KaxTags *>(ElementLevel1));
+
+ } else if (EbmlId(*ElementLevel1) == KaxTracks::ClassInfos.GlobalId) {
+ // Yep, we've found our KaxTracks element. Now find all tracks
+ // contained in this segment.
+ KaxTracks *Tracks = static_cast<KaxTracks *>(ElementLevel1);
+ Tracks->Read(m_InputStream, KaxTracks::ClassInfos.Context, UpperElementLevel, ElementLevel2, bAllowDummy);
+
+ unsigned int Index0;
+ for (Index0 = 0; Index0 < Tracks->ListSize(); Index0++) {
+ if ((*Tracks)[Index0]->Generic().GlobalId == KaxTrackEntry::ClassInfos.GlobalId) {
+ KaxTrackEntry &TrackEntry = *static_cast<KaxTrackEntry *>((*Tracks)[Index0]);
+ // Create a new MatroskaTrack
+ MatroskaTrackInfo newTrack;
+
+ unsigned int Index1;
+ for (Index1 = 0; Index1 < TrackEntry.ListSize(); Index1++) {
+ if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackNumber::ClassInfos.GlobalId) {
+ KaxTrackNumber &TrackNumber = *static_cast<KaxTrackNumber*>(TrackEntry[Index1]);
+ newTrack.trackNumber = TrackNumber;
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackUID::ClassInfos.GlobalId) {
+ KaxTrackUID &TrackUID = *static_cast<KaxTrackUID*>(TrackEntry[Index1]);
+ newTrack.trackUID = TrackUID;
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackType::ClassInfos.GlobalId) {
+ KaxTrackType &TrackType = *static_cast<KaxTrackType*>(TrackEntry[Index1]);
+ if (uint8(TrackType) != track_audio) {
+ newTrack.trackNumber = 0xFFFF;
+ break;
+ }
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackTimecodeScale::ClassInfos.GlobalId) {
+ KaxTrackTimecodeScale &TrackTimecodeScale = *static_cast<KaxTrackTimecodeScale*>(TrackEntry[Index1]);
+ // TODO: Support Tracks with different timecode scales?
+ //newTrack->TrackTimecodeScale = TrackTimecodeScale;
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackDefaultDuration::ClassInfos.GlobalId) {
+ KaxTrackDefaultDuration &TrackDefaultDuration = *static_cast<KaxTrackDefaultDuration*>(TrackEntry[Index1]);
+ newTrack.defaultDuration = (int64)uint64(TrackDefaultDuration);
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxCodecID::ClassInfos.GlobalId) {
+ KaxCodecID &CodecID = *static_cast<KaxCodecID*>(TrackEntry[Index1]);
+ newTrack.codecID = std::string(CodecID);
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxCodecPrivate::ClassInfos.GlobalId) {
+ KaxCodecPrivate &CodecPrivate = *static_cast<KaxCodecPrivate*>(TrackEntry[Index1]);
+ newTrack.codecPrivate.resize(CodecPrivate.GetSize());
+ memcpy(&newTrack.codecPrivate[0], CodecPrivate.GetBuffer(), CodecPrivate.GetSize());
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackFlagDefault::ClassInfos.GlobalId) {
+ KaxTrackFlagDefault &TrackFlagDefault = *static_cast<KaxTrackFlagDefault*>(TrackEntry[Index1]);
+ //newTrack->FlagDefault = TrackFlagDefault;
+ /* Matroska2
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackFlagEnabled::ClassInfos.GlobalId) {
+ KaxTrackFlagEnabled &TrackFlagEnabled = *static_cast<KaxTrackFlagEnabled*>(TrackEntry[Index1]);
+ //newTrack->FlagEnabled = TrackFlagEnabled;
+ */
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackFlagLacing::ClassInfos.GlobalId) {
+ KaxTrackFlagLacing &TrackFlagLacing = *static_cast<KaxTrackFlagLacing*>(TrackEntry[Index1]);
+ //newTrack->FlagLacing = TrackFlagLacing;
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackLanguage::ClassInfos.GlobalId) {
+ KaxTrackLanguage &TrackLanguage = *static_cast<KaxTrackLanguage*>(TrackEntry[Index1]);
+ newTrack.language = std::string(TrackLanguage);
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackMaxCache::ClassInfos.GlobalId) {
+ KaxTrackMaxCache &TrackMaxCache = *static_cast<KaxTrackMaxCache*>(TrackEntry[Index1]);
+ //newTrack->MaxCache = TrackMaxCache;
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackMinCache::ClassInfos.GlobalId) {
+ KaxTrackMinCache &TrackMinCache = *static_cast<KaxTrackMinCache*>(TrackEntry[Index1]);
+ //newTrack->MinCache = TrackMinCache;
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackName::ClassInfos.GlobalId) {
+ KaxTrackName &TrackName = *static_cast<KaxTrackName*>(TrackEntry[Index1]);
+ newTrack.name = TrackName;
+
+ } else if (TrackEntry[Index1]->Generic().GlobalId == KaxTrackAudio::ClassInfos.GlobalId) {
+ KaxTrackAudio &TrackAudio = *static_cast<KaxTrackAudio*>(TrackEntry[Index1]);
+
+ unsigned int Index2;
+ for (Index2 = 0; Index2 < TrackAudio.ListSize(); Index2++) {
+ if (TrackAudio[Index2]->Generic().GlobalId == KaxAudioBitDepth::ClassInfos.GlobalId) {
+ KaxAudioBitDepth &AudioBitDepth = *static_cast<KaxAudioBitDepth*>(TrackAudio[Index2]);
+ newTrack.bitsPerSample = AudioBitDepth;
+ /* Matroska2
+ } else if (TrackAudio[Index2]->Generic().GlobalId == KaxAudioPosition::ClassInfos.GlobalId) {
+ KaxAudioPosition &AudioPosition = *static_cast<KaxAudioPosition*>(TrackAudio[Index2]);
+
+ // TODO: Support multi-channel?
+ //newTrack->audio->ChannelPositionSize = AudioPosition.GetSize();
+ //newTrack->audio->ChannelPosition = new binary[AudioPosition.GetSize()+1];
+ //memcpy(newTrack->audio->ChannelPosition, AudioPosition.GetBuffer(), AudioPosition.GetSize());
+ */
+ } else if (TrackAudio[Index2]->Generic().GlobalId == KaxAudioChannels::ClassInfos.GlobalId) {
+ KaxAudioChannels &AudioChannels = *static_cast<KaxAudioChannels*>(TrackAudio[Index2]);
+ newTrack.channels = AudioChannels;
+
+ } else if (TrackAudio[Index2]->Generic().GlobalId == KaxAudioOutputSamplingFreq::ClassInfos.GlobalId) {
+ KaxAudioOutputSamplingFreq &AudioOutputSamplingFreq = *static_cast<KaxAudioOutputSamplingFreq*>(TrackAudio[Index2]);
+ newTrack.samplesOutputPerSec = AudioOutputSamplingFreq;
+
+ } else if (TrackAudio[Index2]->Generic().GlobalId == KaxAudioSamplingFreq::ClassInfos.GlobalId) {
+ KaxAudioSamplingFreq &AudioSamplingFreq = *static_cast<KaxAudioSamplingFreq*>(TrackAudio[Index2]);
+ newTrack.samplesPerSec = AudioSamplingFreq;
+ }
+ }
+ }
+ }
+ if (newTrack.trackNumber != 0xFFFF)
+ m_Tracks.push_back(newTrack);
+ }
+ }
+ } else if (EbmlId(*ElementLevel1) == KaxCluster::ClassInfos.GlobalId) {
+
+ if (bBreakAtClusters) {
+ m_IOCallback.setFilePointer(ElementLevel1->GetElementPosition());
+ delete ElementLevel1;
+ ElementLevel1 = NULL;
+ break;
+ }
+
+ } else if (EbmlId(*ElementLevel1) == KaxAttachments::ClassInfos.GlobalId) {
+ // Yep, we've found our KaxAttachment element. Now find all attached files
+ // contained in this segment.
+#if 0
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0xFFFFFFFFL, true, 1);
+ while (ElementLevel2 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+ if (EbmlId(*ElementLevel2) == KaxAttached::ClassInfos.GlobalId) {
+ // We actually found a attached file entry :D
+ MatroskaAttachment *newAttachment = new MatroskaAttachment();
+
+ ElementLevel3 = m_InputStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, 0xFFFFFFFFL, true, 1);
+ while (ElementLevel3 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+
+ // Now evaluate the data belonging to this track
+ if (EbmlId(*ElementLevel3) == KaxFileName::ClassInfos.GlobalId) {
+ KaxFileName &attached_filename = *static_cast<KaxFileName *>(ElementLevel3);
+ attached_filename.ReadData(m_InputStream.I_O());
+ newAttachment->FileName = UTFstring(attached_filename).c_str();
+
+ } else if (EbmlId(*ElementLevel3) == KaxMimeType::ClassInfos.GlobalId) {
+ KaxMimeType &attached_mime_type = *static_cast<KaxMimeType *>(ElementLevel3);
+ attached_mime_type.ReadData(m_InputStream.I_O());
+ newAttachment->MimeType = wxString(std::string(attached_mime_type).c_str(), wxConvUTF8);
+
+ } else if (EbmlId(*ElementLevel3) == KaxFileDescription::ClassInfos.GlobalId) {
+ KaxFileDescription &attached_description = *static_cast<KaxFileDescription *>(ElementLevel3);
+ attached_description.ReadData(m_InputStream.I_O());
+ newAttachment->Description = UTFstring(attached_description).c_str();
+
+ } else if (EbmlId(*ElementLevel3) == KaxFileData::ClassInfos.GlobalId) {
+ KaxFileData &attached_data = *static_cast<KaxFileData *>(ElementLevel3);
+
+ //We don't what to read the data into memory because it could be very large
+ //attached_data.ReadData(m_InputStream.I_O());
+
+ //Instead we store the Matroska filename, the start of the data and the length, so we can read it
+ //later at the users request. IMHO This will save a lot of memory
+ newAttachment->SourceFilename = MatroskaFilename;
+ newAttachment->SourceStartPos = attached_data.GetElementPosition() + attached_data.HeadSize();
+ newAttachment->SourceDataLength = attached_data.GetSize();
+ }
+
+ if (UpperElementLevel > 0) { // we're coming from ElementLevel4
+ UpperElementLevel--;
+ delete ElementLevel3;
+ ElementLevel3 = ElementLevel4;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel3->SkipData(m_InputStream, ElementLevel3->Generic().Context);
+ delete ElementLevel3;
+ ElementLevel3 = m_InputStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, 0xFFFFFFFFL, true, 1);
+ }
+ } // while (ElementLevel3 != NULL)
+ m_AttachmentList.push_back(newAttachment);
+ }
+
+ if (UpperElementLevel > 0) { // we're coming from ElementLevel3
+ UpperElementLevel--;
+ delete ElementLevel2;
+ ElementLevel2 = ElementLevel3;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel2->SkipData(m_InputStream, ElementLevel2->Generic().Context);
+ delete ElementLevel2;
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, 0xFFFFFFFFL, true, 1);
+ }
+ } // while (ElementLevel2 != NULL)
+#endif
+ }
+
+
+ if (UpperElementLevel > 0) { // we're coming from ElementLevel2
+ UpperElementLevel--;
+ delete ElementLevel1;
+ ElementLevel1 = ElementLevel2;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel1->SkipData(m_InputStream, ElementLevel1->Generic().Context);
+ delete ElementLevel1;
+ ElementLevel1 = NULL;
+
+ ElementLevel1 = m_InputStream.FindNextElement(m_ElementLevel0->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, true, 1);
+ }
+ } // while (ElementLevel1 != NULL)
+
+ } catch (std::exception &) {
+ return 1;
+
+ } catch (...) {
+ return 1;
+ }
+
+ CountClusters();
+ return 0;
+};
+
+int MatroskaAudioParser::WriteTags(const file_info *info)
+{
+ SetTags(info);
+
+ KaxTags & MyKaxTags = GetChild<KaxTags>(*static_cast<EbmlMaster *>(m_ElementLevel0));
+
+ // On to writing :)
+ if (m_TagPos == 0) {
+ // Ok, we need to append the tags onto the end of the file, no existing tag element
+ m_TagPos = m_FileSize;
+ } else {
+
+ }
+
+ //Start going through the list and adding tags
+ KaxTag *MyKaxTag_last = NULL;
+ for(size_t current_tag_track = 0; current_tag_track < m_Tags.size(); current_tag_track++) {
+ KaxTag *MyKaxTag = NULL;
+ if (MyKaxTag_last != NULL)
+ {
+ MyKaxTag = &GetNextChild<KaxTag>(MyKaxTags, *MyKaxTag_last);
+ MyKaxTag_last = MyKaxTag;
+ }else {
+ MyKaxTag = &GetChild<KaxTag>(MyKaxTags);
+ MyKaxTag_last = MyKaxTag;
+ }
+ MatroskaTagInfo ¤tTag = m_Tags.at(current_tag_track);
+ //The Targets group
+ KaxTagTargets & MyKaxTagTargets = GetChild<KaxTagTargets>(*MyKaxTag);
+ if (currentTag.targetTrackUID != 0) {
+ KaxTagTrackUID & MyKaxTagTrackUID = GetChild<KaxTagTrackUID>(MyKaxTagTargets);
+ *static_cast<EbmlUInteger *>(&MyKaxTagTrackUID) = currentTag.targetTrackUID;
+ }
+ if (currentTag.targetEditionUID != 0) {
+ KaxTagEditionUID & MyKaxTagEditionUID = GetChild<KaxTagEditionUID>(MyKaxTagTargets);
+ *static_cast<EbmlUInteger *>(&MyKaxTagEditionUID) = currentTag.targetEditionUID;
+ }
+ if (currentTag.targetChapterUID != 0) {
+ KaxTagChapterUID & MyKaxTagChapterUID = GetChild<KaxTagChapterUID>(MyKaxTagTargets);
+ *static_cast<EbmlUInteger *>(&MyKaxTagChapterUID) = currentTag.targetChapterUID;
+ }
+ if (currentTag.targetAttachmentUID != 0) {
+ KaxTagAttachmentUID & MyKaxTagAttachmentUID = GetChild<KaxTagAttachmentUID>(MyKaxTagTargets);
+ *static_cast<EbmlUInteger *>(&MyKaxTagAttachmentUID) = currentTag.targetAttachmentUID;
+ }
+ if(currentTag.targetTypeValue != 0) {
+ KaxTagTargetTypeValue& MyKaxTagTargetTypeValue = GetChild<KaxTagTargetTypeValue>(MyKaxTagTargets);
+ *static_cast<EbmlUInteger *>(&MyKaxTagTargetTypeValue) = currentTag.targetTypeValue;
+ if(!currentTag.targetType.empty())
+ {
+ KaxTagTargetType& MyKaxTagTargetType = GetChild<KaxTagTargetType>(MyKaxTagTargets);
+ *static_cast<EbmlString *>(&MyKaxTagTargetType) = currentTag.targetType;
+ }
+ }
+
+ // Add the millions of simple tags we can have ;)
+ KaxTagSimple *MySimpleTag_last = NULL;
+ for(size_t st = 0; st < currentTag.tags.size(); st++) {
+ KaxTagSimple *MySimpleTag = NULL;
+ if (MySimpleTag_last != NULL)
+ {
+ MySimpleTag = &GetNextChild<KaxTagSimple>(*MyKaxTag, *MySimpleTag_last);
+ MySimpleTag_last = MySimpleTag;
+ }else {
+ MySimpleTag = &GetChild<KaxTagSimple>(*MyKaxTag);
+ MySimpleTag_last = MySimpleTag;
+ }
+ MatroskaSimpleTag ¤tSimpleTag = currentTag.tags.at(st);
+
+ KaxTagName & MyKaxTagName = GetChild<KaxTagName>(*MySimpleTag);
+ *static_cast<EbmlUnicodeString *>(&MyKaxTagName) = currentSimpleTag.name;
+
+ KaxTagString & MyKaxTagString = GetChild<KaxTagString>(*MySimpleTag);
+ *static_cast<EbmlUnicodeString *>(&MyKaxTagString) = currentSimpleTag.value;
+
+ KaxTagLangue& MyKaxTagLangue = GetChild<KaxTagLangue>(*MySimpleTag);
+ *static_cast<EbmlString *>(&MyKaxTagLangue) = currentSimpleTag.language;
+
+ KaxTagDefault& MyKaxTagDefault = GetChild<KaxTagDefault>(*MySimpleTag);
+ *static_cast<EbmlUInteger *>(&MyKaxTagDefault) = currentSimpleTag.defaultFlag;
+ }
+
+ m_IOCallback.setFilePointer(m_TagPos);
+ // Now we write the tags to the file
+ EbmlVoid & Dummy = GetChild<EbmlVoid>(*static_cast<EbmlMaster *>(m_ElementLevel0));
+ uint64 size_of_tags = m_TagSize;
+ MyKaxTags.UpdateSize(true);
+ if (size_of_tags < MyKaxTags.GetSize())
+ size_of_tags = MyKaxTags.GetSize();
+
+ Dummy.SetSize(size_of_tags+8); // Size of the previous tag element
+ uint64 pos = m_IOCallback.getFilePointer();
+ if (pos == 0)
+ //We don't want to overwrite the EBML header :P
+ return 2;
+ Dummy.Render(m_IOCallback);
+ m_IOCallback.setFilePointer(Dummy.GetElementPosition());
+ pos = m_IOCallback.getFilePointer();
+ if (pos == 0)
+ //We don't want to overwrite the EBML header :P
+ return 2;
+
+ MyKaxTags.Render(m_IOCallback);
+
+ //l0->UpdateSize(false, true);
+ std::auto_ptr<KaxSegment> new_segment(new KaxSegment);
+ KaxSegment * segment = (KaxSegment *)m_ElementLevel0;
+
+ m_IOCallback.setFilePointer(segment->GetElementPosition());
+ new_segment->WriteHead(m_IOCallback, segment->HeadSize() - 4);
+ m_IOCallback.setFilePointer(0, seek_end);
+ int ret = new_segment->ForceSize(m_IOCallback.getFilePointer() - segment->HeadSize() - segment->GetElementPosition());
+ if (!ret) {
+ segment->OverwriteHead(m_IOCallback);
+ /*("Wrote the element at the end of the file but could "
+ "not update the segment size. Therefore the element "
+ "will not be visible. Aborting the process. The file "
+ "has been changed!")*/
+ m_IOCallback.setFilePointer(segment->HeadSize() - segment->GetElementPosition());
+ m_IOCallback.truncate();
+
+ return 1;
+ } else {
+ new_segment->OverwriteHead(m_IOCallback);
+
+ }
+
+ }
+ return 0;
+};
+
+static const char* foobar2k_to_matroska_edition_tag(const char * name)
+{
+ if (!stricmp_utf8(name, "ALBUM"))
+ return "TITLE";
+ else if (!stricmp_utf8(name, "SUBALBUM"))
+ return "SUBTITLE";
+ else if (starts_with(name, "ALBUM ")) {
+ static char newname[255];
+ ZeroMemory(newname,255);
+ strncpy(newname,name+6,254);
+ name = newname;
+ }
+ for (int i = 0; i < tabsize(edition_tag_mapping); i++)
+ if (!stricmp_utf8(name, edition_tag_mapping[i][FOOBAR2K_TAG_IDX]))
+ return edition_tag_mapping[i][MATROSKA_TAG_IDX];
+ return name;
+}
+
+static const char* foobar2k_to_matroska_chapter_tag(const char * name)
+{
+ for (int i = 0; i < tabsize(chapter_tag_mapping); i++)
+ if (!stricmp_utf8(name, chapter_tag_mapping[i][FOOBAR2K_TAG_IDX]))
+ return chapter_tag_mapping[i][MATROSKA_TAG_IDX];
+ return name;
+}
+
+int meta_get_num(const file_info *info, const char* name, int idx)
+{
+ assert(is_valid_utf8(name));
+ int n, m = min(info->meta_get_count(), idx);
+ int rv = 0;
+ for(n=0; n<m; n++)
+ {
+ if (!stricmp_utf8(name, info->meta_enum_name(n)))
+ rv++;
+ }
+ return rv;
+}
+
+void MatroskaAudioParser::SetTags(const file_info *info)
+{
+ int i, idx;
+ const char *name, *value;
+
+ if (m_Chapters.size() == 0)
+ {
+ // No chapters, works on track
+ MatroskaTagInfo *trackTag;
+ trackTag = FindTagWithTrackUID(m_Tracks.at(m_CurrentTrackNo).trackUID);
+ if (trackTag == NULL) {
+ // The tag doesn't exist yet
+ MatroskaTagInfo tempTag;
+ tempTag.targetTrackUID = m_Tracks.at(m_CurrentTrackNo).trackUID;
+ m_Tags.push_back(tempTag);
+ trackTag = &m_Tags.at(m_Tags.size()-1);
+ }
+ if(trackTag->targetTypeValue == 0)
+ trackTag->targetTypeValue = 50;
+ trackTag->MarkAllAsRemovalPending();
+ int metaDataCount = info->meta_get_count();
+ for (i = 0; i < metaDataCount; i++)
+ {
+ name = info->meta_enum_name(i);
+ value = info->meta_enum_value(i);
+ idx = meta_get_num(info, name, i);
+ name = foobar2k_to_matroska_chapter_tag(name);
+ if ((name != NULL) && (value != NULL)) {
+ trackTag->SetTagValue(name, value, idx);
+ }
+ }
+ // Add the replay_gain tags
+ value = info->info_get("REPLAYGAIN_TRACK_GAIN");
+ if (value)
+ trackTag->SetTagValue("REPLAYGAIN_GAIN", value);
+ value = info->info_get("REPLAYGAIN_TRACK_PEAK");
+ if (value)
+ trackTag->SetTagValue("REPLAYGAIN_PEAK", value);
+ trackTag->RemoveMarkedTags();
+ }
+
+ // Set global track tags as album tags
+ if ((m_Chapters.size() > 0) && (m_CurrentChapter != NULL))
+ {
+ MatroskaTagInfo *trackTag;
+ trackTag = FindTagWithTrackUID(m_Tracks.at(m_CurrentTrackNo).trackUID);
+ if (trackTag == NULL)
+ {
+ MatroskaTagInfo tempTag;
+ tempTag.targetTrackUID = m_Tracks.at(m_CurrentTrackNo).trackUID;
+ m_Tags.push_back(tempTag);
+ trackTag = &m_Tags.at(m_Tags.size()-1);
+ }
+ if(trackTag->targetTypeValue == 0)
+ trackTag->targetTypeValue = 50;
+ trackTag->MarkAllAsRemovalPending();
+ int metaDataCount = info->meta_get_count();
+ for (i = 0; i < metaDataCount; i++)
+ {
+ name = info->meta_enum_name(i);
+ value = info->meta_enum_value(i);
+ idx = meta_get_num(info, name, i);
+ if (starts_with(name, "ALBUM") ||
+ starts_with(name, "SUBALBUM") ||
+ starts_with(name, "DISCID"))
+ {
+ name = foobar2k_to_matroska_edition_tag(name);
+ } else {
+ name = NULL;
+ }
+ if ((name != NULL) && (value != NULL)) {
+ trackTag->SetTagValue(name, value, idx);
+ }
+ }
+ value = info->info_get("REPLAYGAIN_ALBUM_GAIN");
+ if (value)
+ trackTag->SetTagValue("REPLAYGAIN_GAIN", value);
+ value = info->info_get("REPLAYGAIN_ALBUM_PEAK");
+ if (value)
+ trackTag->SetTagValue("REPLAYGAIN_PEAK", value);
+ trackTag->RemoveMarkedTags();
+ }
+
+
+ //If there's not only a track, set all the chapter tags
+ if ((m_Chapters.size() > 0) && (m_CurrentChapter != NULL))
+ {
+ // Ok we add the tags to tag with chapter+track UID's
+ MatroskaTagInfo *chapterTag;
+ chapterTag = FindTagWithChapterUID(m_CurrentChapter->chapterUID,
+ m_Tracks.at(m_CurrentTrackNo).trackUID);
+ if (chapterTag == NULL)
+ {
+ MatroskaTagInfo tempTag;
+ tempTag.targetTrackUID = m_Tracks.at(m_CurrentTrackNo).trackUID;
+ tempTag.targetChapterUID = m_CurrentChapter->chapterUID;
+ m_Tags.push_back(tempTag);
+ chapterTag = &m_Tags.at(m_Tags.size()-1);
+ }
+ if(chapterTag->targetTypeValue == 0)
+ chapterTag->targetTypeValue = 30;
+ chapterTag->MarkAllAsRemovalPending();
+ int metaDataCount = info->meta_get_count();
+ for (i = 0; i < metaDataCount; i++)
+ {
+ name = info->meta_enum_name(i);
+ value = info->meta_enum_value(i);
+ idx = meta_get_num(info, name, i);
+ if (starts_with(name, "ALBUM") ||
+ starts_with(name, "SUBALBUM") ||
+ starts_with(name, "DISCID"))
+ {
+ name = NULL;
+ } else {
+ name = foobar2k_to_matroska_chapter_tag(name);
+ }
+ if ((name != NULL) && (value != NULL)) {
+ chapterTag->SetTagValue(name, value, idx);
+ }
+ }
+ value = info->info_get("REPLAYGAIN_TRACK_GAIN");
+ if (value)
+ chapterTag->SetTagValue("REPLAYGAIN_GAIN", value);
+ value = info->info_get("REPLAYGAIN_TRACK_PEAK");
+ if (value)
+ chapterTag->SetTagValue("REPLAYGAIN_PEAK", value);
+ chapterTag->RemoveMarkedTags();
+ }
+};
+
+MatroskaTagInfo *MatroskaAudioParser::FindTagWithTrackUID(uint64 trackUID)
+{
+ MatroskaTagInfo *foundTag = NULL;
+
+ for (size_t t = 0; t < m_Tags.size(); t++)
+ {
+ MatroskaTagInfo& currentTag = m_Tags.at(t);
+ if (currentTag.targetTrackUID == trackUID &&
+ currentTag.targetEditionUID == 0 &&
+ currentTag.targetChapterUID == 0 &&
+ currentTag.targetAttachmentUID == 0)
+ {
+ foundTag = ¤tTag;
+ break;
+ }
+ }
+
+ return foundTag;
+};
+
+MatroskaTagInfo *MatroskaAudioParser::FindTagWithEditionUID(uint64 editionUID, uint64 trackUID)
+{
+ MatroskaTagInfo *foundTag = NULL;
+
+ for (size_t t = 0; t < m_Tags.size(); t++)
+ {
+ MatroskaTagInfo ¤tTag = m_Tags.at(t);
+ if (currentTag.targetEditionUID == editionUID &&
+ (trackUID == 0 || (currentTag.targetTrackUID == trackUID)))
+ {
+ foundTag = ¤tTag;
+ break;
+ }
+ }
+
+ return foundTag;
+};
+
+MatroskaTagInfo *MatroskaAudioParser::FindTagWithChapterUID(uint64 chapterUID, uint64 trackUID)
+{
+ MatroskaTagInfo *foundTag = NULL;
+
+ for (size_t t = 0; t < m_Tags.size(); t++)
+ {
+ MatroskaTagInfo ¤tTag = m_Tags.at(t);
+ if (currentTag.targetChapterUID == chapterUID &&
+ (trackUID == 0 || (currentTag.targetTrackUID == trackUID)))
+ {
+ foundTag = ¤tTag;
+ break;
+ }
+ }
+
+ return foundTag;
+};
+
+uint64 MatroskaAudioParser::GetDuration() {
+ if (m_CurrentChapter != NULL) {
+ return m_CurrentChapter->timeEnd - m_CurrentChapter->timeStart;
+ };
+ return m_Duration;
+};
+
+int32 MatroskaAudioParser::GetFirstAudioTrack()
+{
+ for (uint16 t = 0; t < m_Tracks.size(); t++)
+ {
+ MatroskaTrackInfo ¤tTrack = m_Tracks.at(t);
+ if (!strncmp(currentTrack.codecID.c_str(),"A_",2)) return t;
+ }
+ return -1;
+}
+
+static bool is_rg_field(const char * name)
+{
+ int m;
+ for (m = 0; m < tabsize(rg_fields); m++)
+ {
+ if (!stricmp_utf8(name,rg_fields[m])) return true;
+ }
+ return false;
+}
+
+static is_hidden_edition_field(const char * name)
+{
+ for (int i = 0; i < tabsize(hidden_edition_field); i++)
+ if (!stricmp_utf8(name, hidden_edition_field[i]))
+ return true;
+ return false;
+}
+
+static is_hidden_chapter_field(const char * name)
+{
+ for (int i = 0; i < tabsize(hidden_chapter_field); i++)
+ if (!stricmp_utf8(name, hidden_chapter_field[i]))
+ return true;
+ return false;
+}
+
+static const char* matroska_to_foobar2k_edition_tag(const char * name)
+{
+ for (int i = 0; i < tabsize(edition_tag_mapping); i++)
+ if (!stricmp_utf8(name, edition_tag_mapping[i][MATROSKA_TAG_IDX]))
+ return edition_tag_mapping[i][FOOBAR2K_TAG_IDX];
+ return name;
+}
+
+static const char* matroska_to_foobar2k_chapter_tag(const char * name)
+{
+ for (int i = 0; i < tabsize(chapter_tag_mapping); i++)
+ if (!stricmp_utf8(name, chapter_tag_mapping[i][MATROSKA_TAG_IDX]))
+ return chapter_tag_mapping[i][FOOBAR2K_TAG_IDX];
+ return name;
+}
+
+static bool IsTagNamed(const MatroskaSimpleTag ¤tSimpleTag, const char * name)
+{
+ return !stricmp_utf8(currentSimpleTag.name.GetUTF8().c_str(), name);
+}
+
+static bool IsTagValued(const MatroskaSimpleTag ¤tSimpleTag, const char * value)
+{
+ return !strcmp(currentSimpleTag.value.GetUTF8().c_str(), value);
+}
+
+static bool AreTagsNameEqual(const MatroskaSimpleTag &tag1, const MatroskaSimpleTag &tag2)
+{
+ return !stricmp_utf8(tag1.name.GetUTF8().c_str(), tag2.name.GetUTF8().c_str());
+}
+
+static bool AreTagsValueEqual(const MatroskaSimpleTag &tag1, const MatroskaSimpleTag &tag2)
+{
+ return !strcmp(tag1.value.GetUTF8().c_str(), tag2.value.GetUTF8().c_str());
+}
+
+static bool AreTagsEqual(const MatroskaSimpleTag &tag1, const MatroskaSimpleTag &tag2)
+{
+ return AreTagsNameEqual(tag1,tag2) && AreTagsValueEqual(tag1,tag2);
+}
+
+const MatroskaSimpleTag* GetTagWithName(MatroskaTagInfo *SimpleTags, const char * name)
+{
+ if(SimpleTags == NULL)
+ return NULL;
+
+ for (size_t s = 0; s < SimpleTags->tags.size(); s++)
+ {
+ const MatroskaSimpleTag ¤tSimpleTag = SimpleTags->tags.at(s);
+ if(IsTagNamed(currentSimpleTag, name))
+ return ¤tSimpleTag;
+ }
+ return NULL;
+}
+
+bool TagExistsAtEditionLevel(MatroskaTagInfo *TrackTags, const char * name)
+{
+ if(TrackTags == NULL)
+ return false;
+
+ for (size_t s = 0; s < TrackTags->tags.size(); s++)
+ {
+ const MatroskaSimpleTag ¤tSimpleTag = TrackTags->tags.at(s);
+ if(IsTagNamed(currentSimpleTag,name))
+ return true;
+ }
+ return false;
+}
+
+bool TagExistsAtChapterLevel(MatroskaTagInfo *ChapterTags, const char * name)
+{
+ if(ChapterTags == NULL)
+ return false;
+
+ for (size_t s = 0; s < ChapterTags->tags.size(); s++)
+ {
+ const MatroskaSimpleTag ¤tSimpleTag = ChapterTags->tags.at(s);
+ if(IsTagNamed(currentSimpleTag,name))
+ return true;
+ }
+ return false;
+}
+
+bool MatroskaAudioParser::AreTagsIdenticalAtAllLevels(const char * name)
+{
+ const MatroskaSimpleTag* ReferenceTag = NULL;
+ bool AtLeastOneChapter = false;
+
+ for (size_t i = 0; i < m_Tags.size(); i++)
+ {
+ MatroskaTagInfo ¤tTags = m_Tags.at(i);
+ if(ReferenceTag == NULL)
+ {
+ AtLeastOneChapter = true;
+ ReferenceTag = GetTagWithName(¤tTags, name);
+ } else {
+ const MatroskaSimpleTag* TagToCheck = GetTagWithName(¤tTags, name);
+ if(TagToCheck && !AreTagsValueEqual(*ReferenceTag,*TagToCheck))
+ return false;
+ }
+ }
+ return AtLeastOneChapter;
+}
+
+bool MatroskaAudioParser::AreTagsIdenticalAtEditionLevel(const char * name)
+{
+ const MatroskaSimpleTag* ReferenceTag = NULL;
+ bool AtLeastOneChapter = false;
+
+ for (size_t i = 0; i < m_Tags.size(); i++)
+ {
+ MatroskaTagInfo ¤tTags = m_Tags.at(i);
+ if (currentTags.targetChapterUID == 0)
+ {
+ // Chapter tags
+ if(ReferenceTag == NULL)
+ {
+ AtLeastOneChapter = true;
+ ReferenceTag = GetTagWithName(¤tTags, name);
+ } else {
+ const MatroskaSimpleTag* TagToCheck = GetTagWithName(¤tTags, name);
+ if(TagToCheck && !AreTagsValueEqual(*ReferenceTag,*TagToCheck))
+ return false;
+ }
+ }
+ }
+ return AtLeastOneChapter;
+}
+
+bool MatroskaAudioParser::AreTagsIdenticalAtChapterLevel(const char * name)
+{
+ const MatroskaSimpleTag* ReferenceTag = NULL;
+ bool AtLeastOneChapter = false;
+
+ for (size_t i = 0; i < m_Tags.size(); i++)
+ {
+ MatroskaTagInfo ¤tTags = m_Tags.at(i);
+ if (currentTags.targetChapterUID != 0)
+ {
+ // Chapter tags
+ if(ReferenceTag == NULL)
+ {
+ AtLeastOneChapter = true;
+ ReferenceTag = GetTagWithName(¤tTags, name);
+ } else {
+ const MatroskaSimpleTag* TagToCheck = GetTagWithName(¤tTags, name);
+ if(TagToCheck && !AreTagsValueEqual(*ReferenceTag,*TagToCheck))
+ return false;
+ }
+ }
+ }
+ return AtLeastOneChapter;
+}
+
+void MatroskaAudioParser::SetAlbumTags(file_info *info,
+ MatroskaTagInfo* AlbumTags,
+ MatroskaTagInfo* TrackTags)
+{
+ if (AlbumTags == NULL)
+ return;
+
+ for (size_t s = 0; s < AlbumTags->tags.size(); s++)
+ {
+ MatroskaSimpleTag &simpleTag = AlbumTags->tags.at(s);
+
+ if (is_rg_field(simpleTag.name.GetUTF8().c_str()))
+ {
+ if(IsTagNamed(simpleTag, "REPLAYGAIN_GAIN"))
+ {
+ info->info_set("REPLAYGAIN_ALBUM_GAIN",
+ simpleTag.value.GetUTF8().c_str());
+ } else if(IsTagNamed(simpleTag, "REPLAYGAIN_PEAK")) {
+ info->info_set("REPLAYGAIN_ALBUM_PEAK",
+ simpleTag.value.GetUTF8().c_str());
+ }
+ }
+ else if (is_hidden_edition_field(simpleTag.name.GetUTF8().c_str()))
+ {
+ // Ignored tag, will be rewrited later
+ simpleTag.hidden = true;
+ }
+ else if(IsTagNamed(simpleTag,"TITLE"))
+ {
+ // Special case for Edition/TITLE
+ if(TagExistsAtChapterLevel(TrackTags, "ALBUM"))
+ {
+ info->meta_add("ALBUM TITLE", simpleTag.value.GetUTF8().c_str());
+ } else {
+ info->meta_add("ALBUM", simpleTag.value.GetUTF8().c_str());
+ }
+ }
+ else if(IsTagNamed(simpleTag,"SUBTITLE"))
+ {
+ // Special case for Edition/SUBTITLE
+ if(TagExistsAtChapterLevel(TrackTags, "SUBALBUM"))
+ {
+ info->meta_add("ALBUM SUBTITLE", simpleTag.value.GetUTF8().c_str());
+ } else {
+ info->meta_add("SUBALBUM", simpleTag.value.GetUTF8().c_str());
+ }
+ }
+ else if(IsTagNamed(simpleTag,"DISCID"))
+ {
+ info->meta_add("DISCID", simpleTag.value.GetUTF8().c_str());
+ }
+ else if(IsTagNamed(simpleTag,"COMMENTS"))
+ {
+ info->meta_add("ALBUM COMMENT", simpleTag.value.GetUTF8().c_str());
+ }
+ else if((!AreTagsIdenticalAtAllLevels(simpleTag.name.GetUTF8().c_str()))
+ || (!TagExistsAtChapterLevel(TrackTags, simpleTag.name.GetUTF8().c_str())))
+ {
+ // Prefix tag with "ALBUM "
+ char newTagName[255] = "ALBUM ";
+ strncat(newTagName, matroska_to_foobar2k_edition_tag(simpleTag.name.GetUTF8().c_str()),255);
+ info->meta_add(newTagName, simpleTag.value.GetUTF8().c_str());
+ }
+ else
+ {
+ // Ignored tag, will be rewrited later
+ simpleTag.hidden = true;
+ }
+ }
+}
+
+void MatroskaAudioParser::SetTrackTags(file_info *info, MatroskaTagInfo* TrackTags)
+{
+ if(TrackTags == NULL)
+ return;
+
+ for (size_t s = 0; s < TrackTags->tags.size(); s++)
+ {
+ MatroskaSimpleTag &simpleTag = TrackTags->tags.at(s);
+
+ if (is_rg_field(simpleTag.name.GetUTF8().c_str()))
+ {
+ if(IsTagNamed(simpleTag, "REPLAYGAIN_GAIN"))
+ {
+ info->info_set("REPLAYGAIN_TRACK_GAIN",
+ simpleTag.value.GetUTF8().c_str());
+ } else if(IsTagNamed(simpleTag, "REPLAYGAIN_PEAK")) {
+ info->info_set("REPLAYGAIN_TRACK_PEAK",
+ simpleTag.value.GetUTF8().c_str());
+ }
+ }
+ else if (is_hidden_chapter_field(simpleTag.name.GetUTF8().c_str()))
+ {
+ // Ignore tag
+ simpleTag.hidden = true;
+ }
+ else if(IsTagNamed(simpleTag,"COMMENTS"))
+ {
+ info->meta_add("COMMENT", simpleTag.value.GetUTF8().c_str());
+ }
+ else if(IsTagNamed(simpleTag,"ALBUM"))
+ {
+ if(!TagExistsAtEditionLevel(TrackTags, "TITLE") && AreTagsIdenticalAtChapterLevel("ALBUM")) {
+ info->meta_add("ALBUM", simpleTag.value.GetUTF8().c_str());
+ } else {
+ info->meta_add("ORIGINAL_ALBUM", simpleTag.value.GetUTF8().c_str());
+ }
+ }
+ else
+ {
+ info->meta_add(
+ matroska_to_foobar2k_chapter_tag(simpleTag.name.GetUTF8().c_str()),
+ simpleTag.value.GetUTF8().c_str());
+ }
+ }
+}
+
+bool MatroskaAudioParser::SetFB2KInfo(file_info *info)
+{
+ if (m_MuxingApp.length() > 0)
+ info->info_set("MUXING_APP", m_MuxingApp.GetUTF8().c_str());
+ if (m_WritingApp.length() > 0)
+ info->info_set("WRITING_APP", m_WritingApp.GetUTF8().c_str());
+ if (m_FileTitle.length() > 0)
+ info->info_set("TITLE", m_FileTitle.GetUTF8().c_str());
+
+ MatroskaTagInfo *TrackTags = FindTagWithTrackUID(m_Tracks.at(m_CurrentTrackNo).trackUID);
+ MatroskaTagInfo *ChapterTags = NULL;
+ if(m_CurrentChapter != NULL)
+ {
+ ChapterTags = FindTagWithChapterUID(m_CurrentChapter->chapterUID,
+ m_Tracks.at(m_CurrentTrackNo).trackUID);
+ }
+ /*
+ MatroskaTagInfo *EditionTags = NULL;
+ if(m_CurrentEdition != NULL)
+ {
+ EditionTags = FindTagWithEditionUID(m_CurrentEdition->editionUID,
+ m_Tracks.at(m_CurrentTrackNo).trackUID);
+ }
+ if(EditionTags != NULL && EditionTags->targetTypeValue == 50)
+ {
+ SetAlbumTags(info, EditionTags, ChapterTags);
+ }
+ */
+ if(TrackTags != NULL)
+ {
+ if(TrackTags->targetTypeValue == 50)
+ {
+ SetAlbumTags(info, TrackTags, ChapterTags);
+ SetTrackTags(info, ChapterTags);
+ } else if (TrackTags->targetTypeValue == 30) {
+ SetTrackTags(info, TrackTags);
+ }
+ }
+
+ // Last chance,
+ if(m_CurrentChapter != NULL)
+ {
+ // If TITLE tag is empty we get it from the chapter name
+ if ((info->meta_get("TITLE") == NULL) || (strlen(info->meta_get("TITLE")) == 0))
+ info->meta_set("TITLE", m_CurrentChapter->display.at(0).string.GetUTF8().c_str());
+ if ((info->meta_get("TRACKNUMBER") == NULL) || (strlen(info->meta_get("TRACKNUMBER")) == 0))
+ {
+ char trackNumberString[32];
+ wsprintf(trackNumberString, "%d",info->get_subsong_index()+1);
+ info->meta_set("TRACKNUMBER", trackNumberString);
+ }
+ if ((info->meta_get("ALBUM") == NULL) || (strlen(info->meta_get("ALBUM")) == 0))
+ {
+ if(m_FileTitle.length() > 0)
+ info->meta_set("ALBUM", m_FileTitle.GetUTF8().c_str());
+ }
+ }
+
+ return true;
+};
+
+void MatroskaAudioParser::MarkHiddenTags()
+{
+ // We call MatroskaAudioParser::SetFB2KInfo with a dummy file_info that
+ // do nothing, so we will only mark hidden tags.
+ // Hidden tag will be copied to keep track of them.
+ // This is needed cause tag are read and written in 2 different instances
+ dummy_file_info info;
+ SetFB2KInfo(&info);
+};
+
+void MatroskaAudioParser::SetCurrentTrack(uint32 newTrackNo)
+{
+ m_CurrentTrackNo = newTrackNo;
+ // Clear the current queue (we are changing tracks)
+ while (!m_Queue.empty())
+ m_Queue.pop();
+};
+
+void MatroskaAudioParser::SetSubSong(int subsong)
+{
+ // As we don't (yet?) use several Editions, select the first (default) one as the current one.
+ m_CurrentEdition = NULL;
+ m_CurrentChapter = NULL;
+ if(m_Editions.size() > 0)
+ m_CurrentEdition = &m_Editions.at(0);
+ if (m_Chapters.size() > subsong)
+ m_CurrentChapter = &m_Chapters.at(subsong);
+
+};
+
+int32 MatroskaAudioParser::GetAvgBitrate()
+{
+ double ret = 0;
+ ret = (double)(int64)m_FileSize / 1024;
+ ret = ret / ((double)(int64)m_Duration / (double)(int64)1000000000);
+ ret = ret * 8;
+ return (int32)ret;
+};
+
+bool MatroskaAudioParser::skip_frames_until(double destination,unsigned & frames,double & last_timecode_delta,unsigned hint_samplerate)
+{
+ unsigned done = 0;
+ unsigned last_laced = 0;
+
+ double last_time;
+ {
+ uint64 last_timecode = get_current_frame_timecode();
+ if (last_timecode == (uint64)(-1)) return false;
+ last_time = TimecodeToSeconds(last_timecode,hint_samplerate);
+ }
+
+ for(;;)
+ {
+ while (!m_Queue.empty()) {
+ MatroskaAudioFrame *currentPacket = m_Queue.front();
+ double packet_time = TimecodeToSeconds(currentPacket->timecode,hint_samplerate);
+ if (packet_time > destination)
+ {
+ if (done==0) return false;
+ last_timecode_delta = destination - last_time;
+ frames = done - last_laced;
+ return true;
+ }
+ last_time = packet_time;
+ done += (last_laced = currentPacket->dataBuffer.size());
+ delete currentPacket;
+ m_Queue.pop();
+ }
+ if (FillQueue()!=0)
+ return false;
+ }
+}
+
+
+void MatroskaAudioParser::flush_queue()
+{
+ while (!m_Queue.empty()) {
+ MatroskaAudioFrame *currentPacket = m_Queue.front();
+ delete currentPacket;
+ m_Queue.pop();
+ }
+}
+
+uint64 MatroskaAudioParser::get_current_frame_timecode()
+{
+ if (m_Queue.empty())
+ {
+ if (FillQueue()!=0) return -1;
+ if (m_Queue.empty()) return -1;
+ }
+ return m_Queue.front()->timecode;
+}
+
+bool MatroskaAudioParser::Seek(double seconds,unsigned & frames_to_skip,double & time_to_skip,unsigned samplerate_hint)
+{
+ int64 ret = 0;
+
+ if (m_CurrentChapter != NULL) {
+ seconds += (double)(int64)m_CurrentChapter->timeStart / 1000000000; // ns -> seconds
+ }
+
+ uint64 seekToTimecode = SecondsToTimecode(seconds);
+
+ flush_queue();
+ m_CurrentTimecode = seekToTimecode;
+
+ if (!skip_frames_until(seconds,frames_to_skip,time_to_skip,samplerate_hint)) return false;
+
+ flush_queue();
+ m_CurrentTimecode = seekToTimecode;
+ if (FillQueue()!=0) return false;
+ return true;
+
+};
+
+MatroskaAudioFrame * MatroskaAudioParser::ReadSingleFrame()
+{
+ for(;;)
+ {
+ if (!m_Queue.empty()) {
+ MatroskaAudioFrame *newFrame = m_Queue.front();
+ m_Queue.pop();
+ return newFrame;
+ } else {
+ if (FillQueue()!=0)
+ return 0;
+ }
+ }
+};
+
+
+void MatroskaAudioParser::Parse_MetaSeek(EbmlElement *metaSeekElement, bool bInfoOnly)
+{
+ uint64 lastSeekPos = 0;
+ uint64 endSeekPos = 0;
+ EbmlElement *l2 = NULL;
+ EbmlElement *l3 = NULL;
+ int UpperElementLevel = 0;
+
+ if (metaSeekElement == NULL)
+ return;
+
+ l2 = m_InputStream.FindNextElement(metaSeekElement->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, true, 1);
+ while (l2 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+
+ if (EbmlId(*l2) == KaxSeek::ClassInfos.GlobalId) {
+ //Wow we found the SeekEntries, time to speed up reading ;)
+ l3 = m_InputStream.FindNextElement(l2->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, true, 1);
+
+ EbmlId *id = NULL;
+ while (l3 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+
+ if (EbmlId(*l3) == KaxSeekID::ClassInfos.GlobalId) {
+ binary *b = NULL;
+ uint16 s = 0;
+ KaxSeekID &seek_id = static_cast<KaxSeekID &>(*l3);
+ seek_id.ReadData(m_InputStream.I_O(), SCOPE_ALL_DATA);
+ b = seek_id.GetBuffer();
+ s = (uint16)seek_id.GetSize();
+ delete id;
+ id = NULL;
+ id = new EbmlId(b, s);
+
+ } else if (EbmlId(*l3) == KaxSeekPosition::ClassInfos.GlobalId) {
+ KaxSeekPosition &seek_pos = static_cast<KaxSeekPosition &>(*l3);
+ seek_pos.ReadData(m_InputStream.I_O());
+ lastSeekPos = uint64(seek_pos);
+ if (endSeekPos < lastSeekPos)
+ endSeekPos = uint64(seek_pos);
+
+ if ((*id == KaxCluster::ClassInfos.GlobalId) && !bInfoOnly) {
+ //NOTE1("Found Cluster Seek Entry Postion: %u", (unsigned long)lastSeekPos);
+ //uint64 orig_pos = inputFile.getFilePointer();
+ MatroskaMetaSeekClusterEntry newCluster;
+ newCluster.timecode = MAX_UINT64;
+ newCluster.filePos = static_cast<KaxSegment *>(m_ElementLevel0)->GetGlobalPosition(lastSeekPos);
+ m_ClusterIndex.push_back(newCluster);
+
+ } else if ((*id == KaxSeekHead::ClassInfos.GlobalId) && !bInfoOnly) {
+ NOTE1("Found MetaSeek Seek Entry Postion: %u", (unsigned long)lastSeekPos);
+ uint64 orig_pos = m_IOCallback.getFilePointer();
+ m_IOCallback.setFilePointer(static_cast<KaxSegment *>(m_ElementLevel0)->GetGlobalPosition(lastSeekPos));
+
+ EbmlElement *levelUnknown = m_InputStream.FindNextID(KaxSeekHead::ClassInfos, 0xFFFFFFFFFFFFFFFFL);
+ Parse_MetaSeek(levelUnknown, false);
+
+ m_IOCallback.setFilePointer(orig_pos);
+ }
+
+ } else {
+
+ }
+ l3->SkipData(m_InputStream, l3->Generic().Context);
+ delete l3;
+ l3 = NULL;
+ l3 = m_InputStream.FindNextElement(l2->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, true, 1);
+ } // while (l3 != NULL)
+ delete id;
+ id = NULL;
+ } else {
+
+ }
+
+ if (UpperElementLevel > 0) { // we're coming from l3
+ UpperElementLevel--;
+ delete l2;
+ l2 = l3;
+ if (UpperElementLevel > 0)
+ break;
+
+ } else {
+ l2->SkipData(m_InputStream, l2->Generic().Context);
+ delete l2;
+ l2 = NULL;
+ l2 = m_InputStream.FindNextElement(metaSeekElement->Generic().Context, UpperElementLevel, 0xFFFFFFFFFFFFFFFFL, true, 1);
+ }
+ } // while (l2 != NULL)
+}
+
+#define IS_ELEMENT_ID(__x__) (Element->Generic().GlobalId == __x__::ClassInfos.GlobalId)
+
+void MatroskaAudioParser::Parse_Chapter_Atom(KaxChapterAtom *ChapterAtom)
+{
+ int i, j;
+ EbmlElement *Element = NULL;
+ MatroskaChapterInfo newChapter;
+
+ NOTE("New chapter");
+
+ for (i = 0; i < ChapterAtom->ListSize(); i++)
+ {
+ Element = (*ChapterAtom)[i];
+
+ if (IS_ELEMENT_ID(KaxChapterUID))
+ {
+ newChapter.chapterUID = uint64(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- UID : %I64d", newChapter.chapterUID);
+ }
+ else if(IS_ELEMENT_ID(KaxChapterTimeStart))
+ {
+ newChapter.timeStart = uint64(*static_cast<EbmlUInteger *>(Element)); // it's in ns
+ NOTE1("- TimeStart : %I64d", newChapter.timeStart);
+ }
+ else if(IS_ELEMENT_ID(KaxChapterTimeEnd))
+ {
+ newChapter.timeEnd = uint64(*static_cast<EbmlUInteger *>(Element)); // it's in ns
+ NOTE1("- TimeEnd : %I64d", newChapter.timeEnd);
+ }
+ else if(IS_ELEMENT_ID(KaxChapterTrack))
+ {
+ KaxChapterTrack *ChapterTrack = (KaxChapterTrack *)Element;
+
+ for (j = 0; j < ChapterTrack->ListSize(); j++)
+ {
+ Element = (*ChapterTrack)[j];
+ if(IS_ELEMENT_ID(KaxChapterTrackNumber))
+ {
+ uint64 chapTrackNo = uint64(*static_cast<EbmlUInteger *>(Element));
+ newChapter.tracks.push_back(chapTrackNo);
+ NOTE1("- TrackNumber : %I64d", chapTrackNo);
+ }
+ else if(IS_ELEMENT_ID(KaxChapterAtom))
+ {
+ // Ignore sub-chapter
+ NOTE("ignore sub-chapter");
+ //Parse_Chapter_Atom((KaxChapterAtom *)Element);
+ }
+ }
+ }
+ else if(IS_ELEMENT_ID(KaxChapterDisplay))
+ {
+ // A new chapter display string+lang+country
+ MatroskaChapterDisplayInfo newChapterDisplay;
+ KaxChapterDisplay *ChapterDisplay = (KaxChapterDisplay *)Element;
+
+ for (j = 0; j < ChapterDisplay->ListSize(); j++) {
+ Element = (*ChapterDisplay)[j];
+ if(IS_ELEMENT_ID(KaxChapterString))
+ {
+ newChapterDisplay.string = UTFstring(*static_cast <EbmlUnicodeString *>(Element)).c_str();
+ NOTE1("- String : %s", newChapterDisplay.string.GetUTF8().c_str());
+ }
+ else if(IS_ELEMENT_ID(KaxChapterAtom))
+ {
+ // Ignore sub-chapter
+ NOTE("ignore sub-chapter");
+ //Parse_Chapter_Atom((KaxChapterAtom *)Element);
+ }
+ }
+ // A emtpy string in a chapter display string is usless
+ if (newChapterDisplay.string.length() > 0)
+ newChapter.display.push_back(newChapterDisplay);
+ }
+ else if(IS_ELEMENT_ID(KaxChapterAtom))
+ {
+ // Ignore sub-chapter
+ NOTE("ignore sub-chapter");
+ //Parse_Chapter_Atom((KaxChapterAtom *)Element);
+ }
+ }
+ if ((newChapter.chapterUID != 0) && !FindChapterUID(newChapter.chapterUID))
+ m_Chapters.push_back(newChapter);
+}
+
+void MatroskaAudioParser::Parse_Chapters(KaxChapters *chaptersElement)
+{
+ int i, j;
+ EbmlElement *Element = NULL;
+ int UpperEltFound = 0;
+
+ NOTE("New edition");
+
+ if (chaptersElement == NULL)
+ return;
+
+ chaptersElement->Read(m_InputStream, KaxChapters::ClassInfos.Context,
+ UpperEltFound, Element, true);
+
+ for (i = 0; i < chaptersElement->ListSize(); i++)
+ {
+ Element = (*chaptersElement)[i];
+ if(IS_ELEMENT_ID(KaxEditionEntry))
+ {
+ MatroskaEditionInfo newEdition;
+ KaxEditionEntry *edition = (KaxEditionEntry *)Element;
+ for (j = 0; j < edition->ListSize(); j++)
+ {
+ Element = (*edition)[j];
+ if(IS_ELEMENT_ID(KaxEditionUID))
+ {
+ // A new edition :)
+ newEdition.editionUID = uint64(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- UID : %I64d", newEdition.editionUID);
+ }
+ else if(IS_ELEMENT_ID(KaxChapterAtom))
+ {
+ // A new chapter :)
+ Parse_Chapter_Atom((KaxChapterAtom *)Element);
+ }
+ }
+ if ((newEdition.editionUID != 0) && !FindEditionUID(newEdition.editionUID))
+ m_Editions.push_back(newEdition);
+ }
+ }
+ FixChapterEndTimes();
+}
+
+void MatroskaAudioParser::Parse_Tags(KaxTags *tagsElement)
+{
+ int i, j, k;
+ EbmlElement *Element = NULL;
+ int UpperEltFound = 0;
+
+ if (tagsElement == NULL)
+ return;
+
+ m_TagPos = tagsElement->GetElementPosition();
+ m_TagSize = tagsElement->GetSize();
+
+ tagsElement->Read(m_InputStream, KaxTags::ClassInfos.Context, UpperEltFound, Element, true);
+
+ for (i = 0; i < tagsElement->ListSize(); i++)
+ {
+ Element = (*tagsElement)[i];
+ if(IS_ELEMENT_ID(KaxTag))
+ {
+ MatroskaTagInfo newTag;
+ newTag.targetTypeValue = 50;
+ KaxTag *tagElement = (KaxTag*)Element;
+ NOTE("New Tag");
+ for (j = 0; j < tagElement->ListSize(); j++)
+ {
+ Element = (*tagElement)[j];
+ if(IS_ELEMENT_ID(KaxTagTargets))
+ {
+ KaxTagTargets *tagTargetsElement = (KaxTagTargets*)Element;
+ for (k = 0; k < tagTargetsElement->ListSize(); k++)
+ {
+ Element = (*tagTargetsElement)[k];
+ if(IS_ELEMENT_ID(KaxTagTrackUID))
+ {
+ newTag.targetTrackUID = uint64(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- TargetTrackUID : %I64d", newTag.targetTrackUID);
+ }
+ else if(IS_ELEMENT_ID(KaxTagEditionUID))
+ {
+ newTag.targetEditionUID = uint64(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- TargetEditionUID : %I64d", newTag.targetEditionUID);
+ }
+ else if(IS_ELEMENT_ID(KaxTagChapterUID))
+ {
+ newTag.targetChapterUID = uint64(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- TargetChapterUIDUID : %I64d", newTag.targetChapterUID);
+ }
+ else if(IS_ELEMENT_ID(KaxTagAttachmentUID))
+ {
+ newTag.targetAttachmentUID = uint64(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- TargetAttachmentUID : %I64d", newTag.targetAttachmentUID);
+ }
+ else if(IS_ELEMENT_ID(KaxTagTargetTypeValue))
+ {
+ newTag.targetTypeValue = uint32(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- TargetTypeValue : %d", newTag.targetTypeValue);
+ }
+ else if(IS_ELEMENT_ID(KaxTagTargetType))
+ {
+ newTag.targetType = std::string(*static_cast<KaxTagTargetType *>(Element));
+ NOTE1("- TargetType : %s", newTag.targetType.c_str());
+ }
+ }
+ }
+ else if(IS_ELEMENT_ID(KaxTagSimple))
+ {
+ MatroskaSimpleTag newSimpleTag;
+ KaxTagSimple *tagSimpleElement = (KaxTagSimple*)Element;
+ NOTE("New SimpleTag");
+ for (k = 0; k < tagSimpleElement->ListSize(); k++)
+ {
+ Element = (*tagSimpleElement)[k];
+ if(IS_ELEMENT_ID(KaxTagName))
+ {
+ newSimpleTag.name = wcsupr((wchar_t *)UTFstring(*static_cast <EbmlUnicodeString *>(Element)).c_str());
+ NOTE1("- Name : %s", newSimpleTag.name.GetUTF8().c_str());
+ }
+ else if(IS_ELEMENT_ID(KaxTagString))
+ {
+ newSimpleTag.value = UTFstring(*static_cast <EbmlUnicodeString *>(Element)).c_str();
+ NOTE1("- Value : %s", newSimpleTag.value.GetUTF8().c_str());
+ }
+ else if(IS_ELEMENT_ID(KaxTagDefault))
+ {
+ newSimpleTag.defaultFlag = uint32(*static_cast<EbmlUInteger *>(Element));
+ NOTE1("- TargetTypeValue : %d", newSimpleTag.default);
+ }
+ else if(IS_ELEMENT_ID(KaxTagLangue))
+ {
+ newSimpleTag.language = std::string(*static_cast <KaxTagLangue *>(Element));
+ NOTE1("- Language : %s", newSimpleTag.language.c_str());
+ }
+ else if(IS_ELEMENT_ID(KaxTagSimple))
+ {
+ // ignore sub-tags
+ // if we want "sub-tags" put this loop in another
+ // function and call it recursively
+ }
+ }
+ newTag.tags.push_back(newSimpleTag);
+ }
+ }
+ m_Tags.push_back(newTag);
+ }
+ }
+};
+
+int MatroskaAudioParser::FillQueue()
+{
+ flush_queue();
+
+ NOTE("MatroskaAudioParser::FillQueue()");
+
+ int UpperElementLevel = 0;
+ bool bAllowDummy = false;
+ // Elements for different levels
+ EbmlElement *ElementLevel1 = NULL;
+ EbmlElement *ElementLevel2 = NULL;
+ EbmlElement *ElementLevel3 = NULL;
+ EbmlElement *ElementLevel4 = NULL;
+ m_framebuffer.set_size(0);
+
+ if (m_IOCallback.seekable()) {
+ MatroskaMetaSeekClusterEntry *currentCluster = FindCluster(m_CurrentTimecode);
+ if (currentCluster == NULL)
+ return 2;
+ int64 clusterFilePos = currentCluster->filePos;
+
+ //console::info(uStringPrintf("cluster %d", currentCluster->clusterNo));
+
+ m_IOCallback.setFilePointer(clusterFilePos);
+ // Find the element data
+ ElementLevel1 = m_InputStream.FindNextID(KaxCluster::ClassInfos, 0xFFFFFFFFFFFFFFFFL);
+ if (ElementLevel1 == NULL)
+ return 1;
+
+ if (EbmlId(*ElementLevel1) == KaxCluster::ClassInfos.GlobalId) {
+ KaxCluster *SegmentCluster = static_cast<KaxCluster *>(ElementLevel1);
+ uint32 ClusterTimecode = 0;
+ MatroskaAudioFrame *prevFrame = NULL;
+
+ // read blocks and discard the ones we don't care about
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, ElementLevel1->ElementSize(), bAllowDummy);
+ while (ElementLevel2 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+ if (EbmlId(*ElementLevel2) == KaxClusterTimecode::ClassInfos.GlobalId) {
+ KaxClusterTimecode & ClusterTime = *static_cast<KaxClusterTimecode*>(ElementLevel2);
+ ClusterTime.ReadData(m_InputStream.I_O());
+ ClusterTimecode = uint32(ClusterTime);
+ currentCluster->timecode = ClusterTimecode * m_TimecodeScale;
+ SegmentCluster->InitTimecode(ClusterTimecode, m_TimecodeScale);
+ } else if (EbmlId(*ElementLevel2) == KaxBlockGroup::ClassInfos.GlobalId) {
+ //KaxBlockGroup & aBlockGroup = *static_cast<KaxBlockGroup*>(ElementLevel2);
+
+ // Create a new frame
+ MatroskaAudioFrame *newFrame = new MatroskaAudioFrame;
+
+ ElementLevel3 = m_InputStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, ElementLevel2->ElementSize(), bAllowDummy);
+ while (ElementLevel3 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+ if (EbmlId(*ElementLevel3) == KaxBlock::ClassInfos.GlobalId) {
+ KaxBlock & DataBlock = *static_cast<KaxBlock*>(ElementLevel3);
+ DataBlock.ReadData(m_InputStream.I_O());
+ DataBlock.SetParent(*SegmentCluster);
+
+ //NOTE4("Track # %u / %u frame%s / Timecode %I64d", DataBlock.TrackNum(), DataBlock.NumberFrames(), (DataBlock.NumberFrames() > 1)?"s":"", DataBlock.GlobalTimecode()/m_TimecodeScale);
+ if (DataBlock.TrackNum() == m_Tracks.at(m_CurrentTrackNo).trackNumber) {
+ newFrame->timecode = DataBlock.GlobalTimecode();
+
+ if (DataBlock.NumberFrames() > 1) {
+ // The evil lacing has been used
+ newFrame->duration = m_Tracks.at(m_CurrentTrackNo).defaultDuration * DataBlock.NumberFrames();
+
+ newFrame->dataBuffer.resize(DataBlock.NumberFrames());
+ for (uint32 f = 0; f < DataBlock.NumberFrames(); f++) {
+ DataBuffer &buffer = DataBlock.GetBuffer(f);
+ newFrame->dataBuffer[f].resize(buffer.Size());
+ memcpy(&newFrame->dataBuffer[f][0], buffer.Buffer(), buffer.Size());
+ }
+ } else {
+ // Non-lacing block
+ newFrame->duration = m_Tracks.at(m_CurrentTrackNo).defaultDuration;
+
+ newFrame->dataBuffer.resize(1);
+ DataBuffer &buffer = DataBlock.GetBuffer(0);
+ newFrame->dataBuffer.at(0).resize(buffer.Size());
+ memcpy(&newFrame->dataBuffer[0][0], buffer.Buffer(), buffer.Size());
+ }
+ } else {
+ //newFrame->timecode = MAX_UINT64;
+ }
+ /*
+ } else if (EbmlId(*ElementLevel3) == KaxReferenceBlock::ClassInfos.GlobalId) {
+ KaxReferenceBlock & RefTime = *static_cast<KaxReferenceBlock*>(ElementLevel3);
+ RefTime.ReadData(m_InputStream.I_O());
+ newFrame->frameReferences.push_back(int32(RefTime));
+ //wxLogDebug(" Reference frame at scaled (%d) timecode %ld\n", int32(RefTime), int32(int64(RefTime) * TimecodeScale));
+ */
+ } else if (EbmlId(*ElementLevel3) == KaxBlockDuration::ClassInfos.GlobalId) {
+ KaxBlockDuration & BlockDuration = *static_cast<KaxBlockDuration*>(ElementLevel3);
+ BlockDuration.ReadData(m_InputStream.I_O());
+ newFrame->duration = uint32(BlockDuration);
+ }
+ if (UpperElementLevel > 0) {
+ UpperElementLevel--;
+ delete ElementLevel3;
+ ElementLevel3 = ElementLevel4;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel3->SkipData(m_InputStream, ElementLevel3->Generic().Context);
+ delete ElementLevel3;
+ ElementLevel3 = NULL;
+
+ ElementLevel3 = m_InputStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, ElementLevel2->ElementSize(), bAllowDummy);
+ }
+ //newFrame = new MatroskaReadFrame();
+ }
+ if (newFrame->dataBuffer.size()>0) {
+ m_Queue.push(newFrame);
+ if (prevFrame != NULL && prevFrame->duration == 0) {
+ prevFrame->duration = newFrame->timecode - prevFrame->timecode;
+ //if (newFrame->duration == 0)
+ // newFrame->duration = prevFrame->duration;
+ }
+
+ // !!!!!!!!!!!!!!! HACK ALERT !!!!!!!!!!!!!!!!!!!!!!!!
+ // This is an ugly hack to keep us from re-seeking to the same cluster
+ m_CurrentTimecode = newFrame->timecode + (newFrame->duration * 2);
+ if (newFrame->duration == 0) {
+ m_CurrentTimecode += (int64)m_Tracks.at(m_CurrentTrackNo).defaultDuration * 2;
+ }
+ // !!!!!!!!!!!!!!! HACK ALERT !!!!!!!!!!!!!!!!!!!!!!!!
+
+ prevFrame = newFrame;
+ }
+ }
+
+ if (UpperElementLevel > 0) {
+ UpperElementLevel--;
+ delete ElementLevel2;
+ ElementLevel2 = ElementLevel3;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel2->SkipData(m_InputStream, ElementLevel2->Generic().Context);
+ //if (ElementLevel2 != pChecksum)
+ delete ElementLevel2;
+ ElementLevel2 = NULL;
+
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, ElementLevel1->ElementSize(), bAllowDummy);
+ }
+ }
+ }
+ delete ElementLevel1;
+
+ if (currentCluster->clusterNo < m_ClusterIndex.size()-1) {
+ if (m_ClusterIndex[currentCluster->clusterNo+1].timecode == MAX_UINT64)
+ m_ClusterIndex[currentCluster->clusterNo+1].timecode = GetClusterTimecode(m_ClusterIndex[currentCluster->clusterNo+1].filePos);
+ m_CurrentTimecode = m_ClusterIndex[currentCluster->clusterNo+1].timecode;
+ if(m_CurrentTimecode == 0)
+ {
+ console::error(uStringPrintf("clusterNo : %d, m_ClusterIndex.size() : %d", currentCluster->clusterNo, m_ClusterIndex.size()));
+ console::info("m_CurrentTimecode == 0 (a)");
+ }
+ } else {
+ m_CurrentTimecode = MAX_UINT64;
+ }
+
+ } else {
+ // Find the element data
+ ElementLevel1 = m_InputStream.FindNextID(KaxCluster::ClassInfos, 0xFFFFFFFFFFFFFFFFL);
+ if (ElementLevel1 == NULL)
+ return 1;
+
+ if (EbmlId(*ElementLevel1) == KaxCluster::ClassInfos.GlobalId) {
+ KaxCluster *SegmentCluster = static_cast<KaxCluster *>(ElementLevel1);
+ uint32 ClusterTimecode = 0;
+
+ // read blocks and discard the ones we don't care about
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, ElementLevel1->ElementSize(), bAllowDummy);
+ while (ElementLevel2 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+ if (EbmlId(*ElementLevel2) == KaxClusterTimecode::ClassInfos.GlobalId) {
+ KaxClusterTimecode & ClusterTime = *static_cast<KaxClusterTimecode*>(ElementLevel2);
+ ClusterTime.ReadData(m_InputStream.I_O());
+ ClusterTimecode = uint32(ClusterTime);
+ SegmentCluster->InitTimecode(ClusterTimecode, m_TimecodeScale);
+ } else if (EbmlId(*ElementLevel2) == KaxBlockGroup::ClassInfos.GlobalId) {
+ //KaxBlockGroup & aBlockGroup = *static_cast<KaxBlockGroup*>(ElementLevel2);
+
+ // Create a new frame
+ MatroskaAudioFrame *newFrame = new MatroskaAudioFrame;
+
+ ElementLevel3 = m_InputStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, ElementLevel2->ElementSize(), bAllowDummy);
+ while (ElementLevel3 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+ if (EbmlId(*ElementLevel3) == KaxBlock::ClassInfos.GlobalId) {
+ KaxBlock & DataBlock = *static_cast<KaxBlock*>(ElementLevel3);
+ DataBlock.ReadData(m_InputStream.I_O());
+ DataBlock.SetParent(*SegmentCluster);
+
+ //NOTE4("Track # %u / %u frame%s / Timecode %I64d", DataBlock.TrackNum(), DataBlock.NumberFrames(), (DataBlock.NumberFrames() > 1)?"s":"", DataBlock.GlobalTimecode()/m_TimecodeScale);
+ if (DataBlock.TrackNum() == m_Tracks.at(m_CurrentTrackNo).trackNumber) {
+ newFrame->timecode = DataBlock.GlobalTimecode();
+
+ if (DataBlock.NumberFrames() > 1) {
+ // The evil lacing has been used
+ newFrame->duration = m_Tracks.at(m_CurrentTrackNo).defaultDuration * DataBlock.NumberFrames();
+
+ newFrame->dataBuffer.resize(DataBlock.NumberFrames());
+ for (uint32 f = 0; f < DataBlock.NumberFrames(); f++) {
+ DataBuffer &buffer = DataBlock.GetBuffer(f);
+ newFrame->dataBuffer[f].resize(buffer.Size());
+ memcpy(&newFrame->dataBuffer[f][0], buffer.Buffer(), buffer.Size());
+ }
+ } else {
+ // Non-lacing block
+ newFrame->duration = m_Tracks.at(m_CurrentTrackNo).defaultDuration;
+
+ newFrame->dataBuffer.resize(1);
+ DataBuffer &buffer = DataBlock.GetBuffer(0);
+ newFrame->dataBuffer.at(0).resize(buffer.Size());
+ memcpy(&newFrame->dataBuffer[0][0], buffer.Buffer(), buffer.Size());
+ }
+ } else {
+ //newFrame->timecode = MAX_UINT64;
+ }
+ /*
+ } else if (EbmlId(*ElementLevel3) == KaxReferenceBlock::ClassInfos.GlobalId) {
+ KaxReferenceBlock & RefTime = *static_cast<KaxReferenceBlock*>(ElementLevel3);
+ RefTime.ReadData(m_InputStream.I_O());
+ newFrame->frameReferences.push_back(int32(RefTime));
+ //wxLogDebug(" Reference frame at scaled (%d) timecode %ld\n", int32(RefTime), int32(int64(RefTime) * TimecodeScale));
+ */
+ } else if (EbmlId(*ElementLevel3) == KaxBlockDuration::ClassInfos.GlobalId) {
+ KaxBlockDuration & BlockDuration = *static_cast<KaxBlockDuration*>(ElementLevel3);
+ BlockDuration.ReadData(m_InputStream.I_O());
+ newFrame->duration = uint32(BlockDuration);
+ }
+ if (UpperElementLevel > 0) {
+ UpperElementLevel--;
+ delete ElementLevel3;
+ ElementLevel3 = ElementLevel4;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel3->SkipData(m_InputStream, ElementLevel3->Generic().Context);
+ delete ElementLevel3;
+ ElementLevel3 = NULL;
+
+ ElementLevel3 = m_InputStream.FindNextElement(ElementLevel2->Generic().Context, UpperElementLevel, ElementLevel2->ElementSize(), bAllowDummy);
+ }
+ //newFrame = new MatroskaReadFrame();
+ }
+ if (newFrame->dataBuffer.size()>0)
+ m_Queue.push(newFrame);
+ }
+
+ if (UpperElementLevel > 0) {
+ UpperElementLevel--;
+ delete ElementLevel2;
+ ElementLevel2 = ElementLevel3;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel2->SkipData(m_InputStream, ElementLevel2->Generic().Context);
+ //if (ElementLevel2 != pChecksum)
+ delete ElementLevel2;
+ ElementLevel2 = NULL;
+
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, ElementLevel1->ElementSize(), bAllowDummy);
+ }
+ }
+ }
+ ElementLevel1->SkipData(m_InputStream, ElementLevel1->Generic().Context);
+ delete ElementLevel1;
+ }
+ //NOTE1("MatroskaAudioParser::FillQueue() - Queue now has %u frames", m_Queue.size());
+ return 0;
+};
+
+uint64 MatroskaAudioParser::GetClusterTimecode(uint64 filePos) {
+ try {
+ uint64 ret = MAX_UINT64;
+
+ int UpperElementLevel = 0;
+ // Elements for different levels
+ EbmlElement *ElementLevel1 = NULL;
+ EbmlElement *ElementLevel2 = NULL;
+ EbmlElement *ElementLevel3 = NULL;
+
+ m_IOCallback.setFilePointer(filePos);
+ // Find the element data
+ ElementLevel1 = m_InputStream.FindNextID(KaxCluster::ClassInfos, 0xFFFFFFFFFFFFFFFFL);
+ if (ElementLevel1 == NULL)
+ return MAX_UINT64;
+
+ if (EbmlId(*ElementLevel1) == KaxCluster::ClassInfos.GlobalId) {
+ //KaxCluster *SegmentCluster = static_cast<KaxCluster *>(ElementLevel1);
+ //uint32 ClusterTimecode = 0;
+
+ // read blocks and discard the ones we don't care about
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, ElementLevel1->ElementSize(), false);
+ while (ElementLevel2 != NULL) {
+ if (UpperElementLevel > 0) {
+ break;
+ }
+ if (UpperElementLevel < 0) {
+ UpperElementLevel = 0;
+ }
+ if (EbmlId(*ElementLevel2) == KaxClusterTimecode::ClassInfos.GlobalId) {
+ KaxClusterTimecode & ClusterTime = *static_cast<KaxClusterTimecode*>(ElementLevel2);
+ ClusterTime.ReadData(m_InputStream.I_O());
+ ret = uint64(ClusterTime) * m_TimecodeScale;
+
+ }
+
+ if (UpperElementLevel > 0) {
+ UpperElementLevel--;
+ delete ElementLevel2;
+ ElementLevel2 = ElementLevel3;
+ if (UpperElementLevel > 0)
+ break;
+ } else {
+ ElementLevel2->SkipData(m_InputStream, ElementLevel2->Generic().Context);
+ //if (ElementLevel2 != pChecksum)
+ delete ElementLevel2;
+ ElementLevel2 = NULL;
+ if (ret == -1)
+ ElementLevel2 = m_InputStream.FindNextElement(ElementLevel1->Generic().Context, UpperElementLevel, ElementLevel1->ElementSize(), false);
+ }
+ }
+ }
+ delete ElementLevel1;
+
+ return ret;
+ } catch (...) {
+ return MAX_UINT64;
+ }
+};
+
+MatroskaMetaSeekClusterEntry *MatroskaAudioParser::FindCluster(uint64 timecode)
+{
+ try {
+ #ifdef _DEBUG_NO_SEEKING
+ static size_t callCount = 0;
+ if (callCount < m_ClusterIndex.size())
+ return &m_ClusterIndex[callCount++];
+ else return NULL;
+ #endif
+
+ if (timecode == 0)
+ // Special case
+ return &m_ClusterIndex.at(0);
+
+ MatroskaMetaSeekClusterEntry *correctEntry = NULL;
+ double clusterDuration = (double)(int64)m_ClusterIndex.size() / (double)(int64)m_Duration;
+ size_t clusterIndex = clusterDuration * (double)(int64)timecode;
+ //int lookCount = 0;
+ if (clusterIndex > m_ClusterIndex.size())
+ clusterIndex = m_ClusterIndex.size()-1;
+
+ MatroskaMetaSeekClusterEntry *clusterEntry = NULL;
+ MatroskaMetaSeekClusterEntry *prevClusterEntry = NULL;
+ MatroskaMetaSeekClusterEntry *nextClusterEntry = NULL;
+ while (correctEntry == NULL) {
+ clusterEntry = &m_ClusterIndex.at(clusterIndex);
+ if (clusterIndex > 0)
+ prevClusterEntry = &m_ClusterIndex.at(clusterIndex-1);
+ if (clusterIndex+1 < m_ClusterIndex.size())
+ nextClusterEntry = &m_ClusterIndex.at(clusterIndex+1);
+
+ // We need timecodes to do good seeking
+ if (clusterEntry->timecode == MAX_UINT64) {
+ clusterEntry->timecode = GetClusterTimecode(clusterEntry->filePos);
+ }
+ if (prevClusterEntry != NULL && prevClusterEntry->timecode == MAX_UINT64) {
+ prevClusterEntry->timecode = GetClusterTimecode(prevClusterEntry->filePos);
+ }
+ if (nextClusterEntry != NULL && nextClusterEntry->timecode == MAX_UINT64) {
+ nextClusterEntry->timecode = GetClusterTimecode(nextClusterEntry->filePos);
+ }
+
+ if (clusterEntry->timecode == timecode) {
+ // WOW, we are seeking directly to this cluster
+ correctEntry = clusterEntry;
+ break;
+ }
+
+ if (prevClusterEntry != NULL) {
+ if (clusterEntry->timecode > timecode && timecode > prevClusterEntry->timecode) {
+ // We found it !!!
+ correctEntry = prevClusterEntry;
+ break;
+ }
+ if (prevClusterEntry->timecode == timecode) {
+ // WOW, we are seeking directly to this cluster
+ correctEntry = prevClusterEntry;
+ break;
+ }
+ // Check if we overshot the needed cluster
+ if (timecode < prevClusterEntry->timecode) {
+ clusterIndex--;
+ //lookCount++; // This is how many times we have 'looked'
+ continue;
+ }
+ }
+
+ if (nextClusterEntry != NULL) {
+ if (clusterEntry->timecode < timecode && timecode < nextClusterEntry->timecode) {
+ // We found it !!!
+ correctEntry = clusterEntry;
+ break;
+ }
+ if (nextClusterEntry->timecode == timecode) {
+ // WOW, we are seeking directly to this cluster
+ correctEntry = nextClusterEntry;
+ break;
+ }
+ // Check if we undershot the needed cluster
+ if (timecode > nextClusterEntry->timecode) {
+ clusterIndex++;
+ //lookCount--;
+ continue;
+ }
+ }
+ // We should never get here, unless this is the last cluster
+ assert(clusterEntry != NULL);
+ if (timecode <= m_Duration)
+ correctEntry = clusterEntry;
+ else
+ break;
+ }
+
+ if (correctEntry != NULL)
+ NOTE3("MatroskaAudioParser::FindCluster(timecode = %u) seeking to cluster %i at %u", (uint32)(timecode / m_TimecodeScale), (uint32)correctEntry->clusterNo, (uint32)correctEntry->filePos);
+ else
+ NOTE1("MatroskaAudioParser::FindCluster(timecode = %u) seeking failed", (uint32)(timecode / m_TimecodeScale));
+
+ return correctEntry;
+ } catch (...) {
+ return NULL;
+ }
+}
+
+void MatroskaAudioParser::CountClusters()
+{
+ for (uint32 c = 0; c < m_ClusterIndex.size(); c++) {
+ MatroskaMetaSeekClusterEntry &clusterEntry = m_ClusterIndex.at(c);
+ clusterEntry.clusterNo = c;
+ }
+}
+
+void MatroskaAudioParser::FixChapterEndTimes()
+{
+ if (m_Chapters.size() > 0) {
+ MatroskaChapterInfo *nextChapter = &m_Chapters.at(m_Chapters.size()-1);
+ if (nextChapter->timeEnd == 0) {
+ nextChapter->timeEnd = m_Duration;
+ }
+ for (uint32 c = 0; c < m_Chapters.size()-1; c++) {
+ MatroskaChapterInfo ¤tChapter = m_Chapters.at(c);
+ nextChapter = &m_Chapters.at(c+1);
+ if (currentChapter.timeEnd == 0) {
+ currentChapter.timeEnd = nextChapter->timeStart;
+ }
+ }
+ nextChapter = &m_Chapters.at(m_Chapters.size()-1);
+ if ((nextChapter->timeEnd == 0) || (nextChapter->timeEnd == nextChapter->timeStart)) {
+ nextChapter->timeEnd = m_Duration;
+ }
+ }
+}
+
+bool MatroskaAudioParser::FindEditionUID(uint64 uid)
+{
+ for (uint32 c = 0; c < m_Editions.size(); c++) {
+ MatroskaEditionInfo ¤tEdition = m_Editions.at(c);
+ if (currentEdition.editionUID == uid)
+ return true;
+ }
+ return false;
+}
+
+bool MatroskaAudioParser::FindChapterUID(uint64 uid)
+{
+ for (uint32 c = 0; c < m_Chapters.size(); c++) {
+ MatroskaChapterInfo ¤tChapter = m_Chapters.at(c);
+ if (currentChapter.chapterUID == uid)
+ return true;
+ }
+ return false;
+}
+
+void PrintChapters(std::vector<MatroskaChapterInfo> &theChapters)
+{
+ for (uint32 c = 0; c < theChapters.size(); c++) {
+ MatroskaChapterInfo ¤tChapter = theChapters.at(c);
+ NOTE2("Chapter %u, UID: %u", c, (uint32)currentChapter.chapterUID);
+ NOTE1("\tStart Time: %u", (uint32)currentChapter.timeStart);
+ NOTE1("\tEnd Time: %u", (uint32)currentChapter.timeEnd);
+ for (uint32 d = 0; d < currentChapter.display.size(); d++) {
+ NOTE3("\tDisplay %u, String: %s Lang: %s", d, currentChapter.display.at(d).string.GetUTF8().c_str(), currentChapter.display.at(d).lang.c_str());
+ }
+ for (uint32 t = 0; t < currentChapter.tracks.size(); t++) {
+ NOTE2("\tTrack %u, UID: %%u", t, (uint32)currentChapter.tracks.at(t));
+ }
+
+ }
+};
Deleted: tags/foo_matroska_0.8/matroska_parser.h
Copied: tags/foo_matroska_0.8/matroska_parser.h (from rev 904, trunk/foo_matroska/matroska_parser.h)
===================================================================
--- trunk/foo_matroska/matroska_parser.h 2004-10-22 19:49:54 UTC (rev 904)
+++ tags/foo_matroska_0.8/matroska_parser.h 2004-10-26 20:44:54 UTC (rev 924)
@@ -0,0 +1,372 @@
+/*
+ * Part of the foobar2000 Matroska plugin
+ *
+ * Copyright (C) Jory Stone (jcsston at toughguy net) - 2003-2004
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*!
+ \file matroska_parser.h
+ \version $Id$
+ \brief An audio slated Matroska Reader+Parser
+ \author Jory Stone <jcsston @ toughguy.net>
+*/
+
+#ifndef _MATROSKA_PARSER_H_
+#define _MATROSKA_PARSER_H_
+
+#include "../SDK/foobar2000.h"
+#include "Foobar2000ReaderIOCallback.h"
+#include "DbgOut.h"
+#include <queue>
+#include <deque>
+
+// libebml includes
+#include "ebml/StdIOCallback.h"
+#include "ebml/EbmlTypes.h"
+#include "ebml/EbmlHead.h"
+#include "ebml/EbmlVoid.h"
+#include "ebml/EbmlCrc32.h"
+#include "ebml/EbmlSubHead.h"
+#include "ebml/EbmlStream.h"
+#include "ebml/EbmlBinary.h"
+#include "ebml/EbmlString.h"
+#include "ebml/EbmlUnicodeString.h"
+#include "ebml/EbmlContexts.h"
+#include "ebml/EbmlVersion.h"
+
+// libmatroska includes
+#include "matroska/KaxConfig.h"
+#include "matroska/KaxBlock.h"
+#include "matroska/KaxSegment.h"
+#include "matroska/KaxContexts.h"
+#include "matroska/KaxSeekHead.h"
+#include "matroska/KaxTracks.h"
+#include "matroska/KaxInfo.h"
+#include "matroska/KaxInfoData.h"
+#include "matroska/KaxTags.h"
+#include "matroska/KaxTag.h"
+#include "matroska/KaxTagMulti.h"
+#include "matroska/KaxCluster.h"
+#include "matroska/KaxClusterData.h"
+#include "matroska/KaxTrackAudio.h"
+#include "matroska/KaxTrackVideo.h"
+#include "matroska/KaxAttachments.h"
+#include "matroska/KaxAttached.h"
+#include "matroska/KaxChapters.h"
+#include "matroska/KaxVersion.h"
+
+#define TIMECODE_SCALE 1000000
+#define MAX_UINT64 0xFFFFFFFFFFFFFFFF
+
+//Memory Leak Debuging define
+#ifdef _DEBUG
+#define _CRTDBG_MAP_ALLOC
+#include <stdlib.h>
+#include <crtdbg.h>
+#endif
+
+#ifdef _DEBUG
+ #define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__)
+#else
+ #define DEBUG_CLIENTBLOCK
+#endif // _DEBUG
+
+#ifdef _DEBUG
+#define new DEBUG_CLIENTBLOCK
+#endif
+
+using namespace LIBEBML_NAMESPACE;
+using namespace LIBMATROSKA_NAMESPACE;
+
+typedef std::vector<uint8> ByteArray;
+
+class MatroskaAudioFrame {
+public:
+ MatroskaAudioFrame();
+ void Reset();
+
+ uint64 timecode;
+ uint32 duration;
+ std::vector<ByteArray> dataBuffer;
+ /// Linked-list for laced frames
+};
+
+
+struct MatroskaMetaSeekClusterEntry {
+ uint32 clusterNo;
+ uint64 filePos;
+ uint64 timecode;
+};
+
+class MatroskaSimpleTag {
+public:
+ MatroskaSimpleTag();
+
+ UTFstring name;
+ UTFstring value;
+ uint32 defaultFlag;
+ std::string language;
+
+ bool hidden;
+ bool removalPending;
+};
+
+class MatroskaTagInfo {
+public:
+ MatroskaTagInfo();
+ void SetTagValue(const char *name, const char *value, int index = 0);
+ void MarkAllAsRemovalPending();
+ void RemoveMarkedTags();
+
+ uint64 targetTrackUID;
+ uint64 targetEditionUID;
+ uint64 targetChapterUID;
+ uint64 targetAttachmentUID;
+ uint32 targetTypeValue;
+ std::string targetType;
+
+ std::vector<MatroskaSimpleTag> tags;
+};
+
+struct MatroskaChapterDisplayInfo {
+ MatroskaChapterDisplayInfo();
+
+ UTFstring string;
+ std::string lang;
+ std::string country;
+};
+
+class MatroskaChapterInfo {
+public:
+ MatroskaChapterInfo();
+
+ uint64 chapterUID;
+ uint64 timeStart;
+ uint64 timeEnd;
+ /// Vector of all the tracks this chapter applies to
+ /// if it's empty then this chapter applies to all tracks
+ std::vector<uint64> tracks;
+ /// Vector of strings we can display for chapter
+ std::vector<MatroskaChapterDisplayInfo> display;
+};
+
+class MatroskaEditionInfo {
+public:
+ MatroskaEditionInfo();
+
+ uint64 editionUID;
+ /// Vector of all the tracks this edition applies to
+ /// if it's empty then this edition applies to all tracks
+ std::vector<uint64> tracks;
+};
+
+class MatroskaTrackInfo {
+ public:
+ /// Initializes the class
+ MatroskaTrackInfo();
+ /// Initializes the class with a UID
+ /// \param trackUID The UID to use for the new MatroskaTrackInfo
+ //MatroskaTrackInfo(uint64 trackUID);
+ /// Destroys the class
+ //~MatroskaTrackInfo();
+
+
+ uint16 trackNumber;
+ uint64 trackUID;
+ std::string codecID;
+ std::vector<BYTE> codecPrivate;
+ bool codecPrivateReady;
+
+ UTFstring name;
+ std::string language;
+ double duration;
+
+ uint8 channels;
+ float samplesPerSec;
+ float samplesOutputPerSec;
+ uint8 bitsPerSample;
+ uint32 avgBytesPerSec;
+ float defaultDuration;
+};
+
+class MatroskaAudioParser {
+public:
+ MatroskaAudioParser(reader *input);
+ ~MatroskaAudioParser();
+
+ /// The main header parsing function
+ /// \return 0 File parsed ok
+ /// \return 1 Failed
+ int Parse(bool bInfoOnly = false, bool bBreakAtClusters = true);
+ /// Writes the tags to the current matroska file
+ /// \param info All the tags we need to write
+ /// \return 0 Tags written A OK
+ /// \return 1 Failed to write tags
+ int WriteTags(const file_info *info);
+
+ MatroskaTrackInfo &GetTrack(uint16 trackNo) { return m_Tracks.at(trackNo); };
+ uint64 GetTimecodeScale() { return m_TimecodeScale; };
+ /// Returns an adjusted duration of the file
+ uint64 GetDuration();
+ /// Returns the track index of the first decodable track
+ int32 GetFirstAudioTrack();
+
+ double TimecodeToSeconds(uint64 code,unsigned samplerate_hint);
+ uint64 SecondsToTimecode(double seconds);
+
+ /// Set the fb2k info from the matroska file
+ /// \param info This will be filled up with tags ;)
+ bool SetFB2KInfo(file_info *info);
+
+ /// Get the foobar2000 style format string
+// const char *GetFoobar2000Format(uint16 trackNo, bool bSetupCodecPrivate = true);
+
+ /// Set the current track to read data from
+ void SetCurrentTrack(uint32 newTrackNo);
+ /// Set the subsong to play, this adjusts all the duration/timecodes
+ /// reported in public functions. So only use this if you are expecting that to happen
+ /// \param subsong This should be within the range of the chapters vector
+ void SetSubSong(int subsong);
+
+ std::vector<MatroskaEditionInfo> &GetEditions() { return m_Editions; };
+ std::vector<MatroskaChapterInfo> &GetChapters() { return m_Chapters; };
+
+ int32 GetAvgBitrate();
+ /// Seek to a position
+ /// \param seconds The absolute position to seek to, in seconds
+ /// \return Current postion offset from where it was requested
+ /// If you request to seek to 2.0 and we can only seek to 1.9
+ /// the return value would be 100 * m_TimcodeScale
+
+ bool skip_frames_until(double destination,unsigned & frames,double & last_timecode_delta,unsigned hint_samplerate);
+ void flush_queue();
+ uint64 get_current_frame_timecode();
+ bool Seek(double seconds,unsigned & frames_to_skip,double & time_to_skip,unsigned samplerate_hint);
+
+ /// Seek to a position
+ /// \param frame The MatroskaAudioFrame struct to store the frame
+ /// \return 0 If read ok
+ /// \return 1 If file could not be read or it not open
+ /// \return 2 End of track (EOT)
+ MatroskaAudioFrame * ReadSingleFrame();
+
+protected:
+ void Parse_MetaSeek(EbmlElement *metaSeekElement, bool bInfoOnly);
+ void Parse_Chapters(KaxChapters *chaptersElement);
+ void Parse_Chapter_Atom(KaxChapterAtom *ChapterAtom);
+ void Parse_Tags(KaxTags *tagsElement);
+ int FillQueue();
+ uint64 GetClusterTimecode(uint64 filePos);
+ MatroskaMetaSeekClusterEntry *FindCluster(uint64 timecode);
+ void CountClusters();
+ void FixChapterEndTimes();
+ // See if the edition uid is already in our vector
+ // \return true Yes, we already have this uid
+ // \return false Nope
+ bool FindEditionUID(uint64 uid);
+ // See if the chapter uid is already in our vector
+ // \return true Yes, we already have this uid
+ // \return false Nope
+ bool FindChapterUID(uint64 uid);
+ /// Adds the info tags to the current file in memory
+ void AddTags(const file_info *info);
+ /// Set the info tags to the current tags file in memory
+ void SetTags(const file_info *info);
+ MatroskaTagInfo *FindTagWithTrackUID(uint64 trackUID);
+ MatroskaTagInfo *FindTagWithEditionUID(uint64 editionUID, uint64 trackUID = 0);
+ MatroskaTagInfo *FindTagWithChapterUID(uint64 chapterUID, uint64 trackUID = 0);
+ bool AreTagsIdenticalAtAllLevels(const char * name);
+ bool AreTagsIdenticalAtEditionLevel(const char * name);
+ bool AreTagsIdenticalAtChapterLevel(const char * name);
+ void MarkHiddenTags();
+
+ void SetAlbumTags(file_info *info, MatroskaTagInfo* AlbumTags, MatroskaTagInfo* TrackTags);
+ void SetTrackTags(file_info *info, MatroskaTagInfo* TrackTags);
+
+
+ Foobar2000ReaderIOCallback m_IOCallback;
+ EbmlStream m_InputStream;
+ /// The main/base/master element, should be the segment
+ EbmlElement *m_ElementLevel0;
+
+ MatroskaEditionInfo *m_CurrentEdition;
+ MatroskaChapterInfo *m_CurrentChapter;
+ uint32 m_CurrentTrackNo;
+ std::vector<MatroskaTrackInfo> m_Tracks;
+ std::vector<MatroskaEditionInfo> m_Editions;
+ std::vector<MatroskaChapterInfo> m_Chapters;
+ std::vector<MatroskaTagInfo> m_Tags;
+
+ /// This is the queue of buffered frames to deliver
+ std::queue<MatroskaAudioFrame *> m_Queue;
+
+ /// This is the index of clusters in the file, it's used to seek in the file
+ std::vector<MatroskaMetaSeekClusterEntry> m_ClusterIndex;
+
+ uint64 m_CurrentTimecode;
+ uint64 m_Duration;
+ uint64 m_TimecodeScale;
+ UTFstring m_WritingApp;
+ UTFstring m_MuxingApp;
+ UTFstring m_FileTitle;
+ int32 m_FileDate;
+ UTFstring m_SegmentFilename;
+
+ uint64 m_FileSize;
+ uint64 m_TagPos;
+ uint32 m_TagSize;
+ uint32 m_TagScanRange;
+
+ mem_block_fastalloc<BYTE> m_framebuffer;
+ //int UpperElementLevel;
+private:
+ // We have no need for these
+ //MatroskaAudioParser(const MatroskaAudioParser &refParser) { };
+ //MatroskaAudioParser &operator=(const MatroskaAudioParser &refParser) { return *this; };
+};
+
+void PrintChapters(std::vector<MatroskaChapterInfo> &theChapters);
+
+class dummy_playable_location : public playable_location
+{
+ const char * get_path() const { return NULL; }
+ void set_path(const char*) {}
+ int get_number() const { return 0; }
+ void set_number(int) { };
+};
+
+class dummy_file_info : public file_info
+{
+private:
+ dummy_playable_location m_pl;
+public:
+ int meta_get_count(void) const { return 0; }
+ const char *meta_enum_name(int) const { return NULL; }
+ const char *meta_enum_value(int) const { return NULL; }
+ void meta_modify_value(int,const char *) { }
+ void meta_insert(int,const char *,const char *) { }
+ void meta_add(const char *,const char *) { }
+ void meta_remove(int) { }
+ void meta_remove_all(void) { }
+ void info_set(const char *,const char *) { }
+ int info_get_count(void) const { return 0; }
+ const char *info_enum_name(int) const { return NULL; }
+ const char *info_enum_value(int) const { return NULL; }
+ void info_remove(int) { }
+ void info_remove_all(void) { }
+ const class playable_location *get_location(void) const { return &m_pl; }
+ void set_location(const class playable_location *) { }
+ void set_length(double) { }
+ double get_length(void) const { return 0; }
+};
+
+#endif // _MATROSKA_PARSER_H_
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.