diff --git a/core/ass_file.h b/core/ass_file.h index a5789f81e..0b030447c 100644 --- a/core/ass_file.h +++ b/core/ass_file.h @@ -82,7 +82,7 @@ public: void FlagAsModified(); // Flag file as being modified, will automatically put a copy on stack void Clear(); // Wipes file void CompressForStack(bool compress); // Compress/decompress for storage on stack - void LoadDefault(bool noline=true); // Loads default file. Pass true to prevent it from adding a default line too + void LoadDefault(bool defline=true); // Loads default file. Pass false to prevent it from adding a default line too void InsertStyle(AssStyle *style); // Inserts a style to file void InsertAttachment(AssAttachment *attach); // Inserts an attachment void InsertAttachment(wxString filename); // Inserts a file as an attachment diff --git a/core/changelog.txt b/core/changelog.txt index 1f3d705e3..9d26e4d58 100644 --- a/core/changelog.txt +++ b/core/changelog.txt @@ -23,6 +23,7 @@ Please visit http://aegisub.net to download latest version - You can now no longer create a style storage whose name is invalid as a filename, instead the illegal characters are replaced with a safe character and a warning is displayed (jfs) o Previously those storages seemed to be created correctly, but were never written to disk and thus lost - Added a "Auto Save on Every Change" option to config.dat (default false), that automatically saves the files whenever you change anything. (AMZ) +- Added support for reading ASS, SSA and SRT softsubs directly from Matroska files. (AMZ) = 1.10 beta - 2006.08.07 =========================== diff --git a/core/mkv_wrap.cpp b/core/mkv_wrap.cpp index 8a0265f81..d6abe09a7 100644 --- a/core/mkv_wrap.cpp +++ b/core/mkv_wrap.cpp @@ -38,8 +38,12 @@ // Headers #include #include +#include +#include #include "mkv_wrap.h" #include "dialog_progress.h" +#include "ass_file.h" +#include "ass_time.h" //////////// @@ -68,7 +72,7 @@ MatroskaWrapper::~MatroskaWrapper() { ///////////// // Open file -void MatroskaWrapper::Open(wxString filename) { +void MatroskaWrapper::Open(wxString filename,bool parse) { // Make sure it's closed first Close(); @@ -84,7 +88,8 @@ void MatroskaWrapper::Open(wxString filename) { throw wxString(_T("MatroskaParser error: ") + wxString(err,wxConvUTF8)).c_str(); } - Parse(); + // Parse + if (parse) Parse(); } // Failed opening @@ -241,6 +246,204 @@ void MatroskaWrapper::SetToTimecodes(FrameRate &target) { } +///////////////// +// Get subtitles +void MatroskaWrapper::GetSubtitles(AssFile *target) { + // Get info + int tracks = mkv_GetNumTracks(file); + TrackInfo *trackInfo; + SegmentInfo *segInfo = mkv_GetFileInfo(file); + wxArrayInt tracksFound; + wxArrayString tracksNames; + int trackToRead = -1; + + // Haali's library variables + ulonglong startTime, endTime, filePos; + unsigned int rt, frameSize, frameFlags; + CompressedStream *cs = NULL; + + // Find tracks + for (int track=0;trackType == 0x11) { + wxString CodecID = wxString(trackInfo->CodecID,*wxConvCurrent); + wxString TrackName = wxString(trackInfo->Name,*wxConvCurrent); + wxString TrackLanguage = wxString(trackInfo->Language,*wxConvCurrent); + + // Known subtitle format + if (CodecID == _T("S_TEXT/SSA") || CodecID == _T("S_TEXT/ASS") || CodecID == _T("S_TEXT/UTF8")) { + tracksFound.Add(track); + tracksNames.Add(wxString::Format(_T("%i ("),track) + CodecID + _T(" ") + TrackLanguage + _T("): ") + TrackName); + } + } + } + + // No tracks found + if (tracksFound.Count() == 0) { + target->LoadDefault(true); + Close(); + throw _T("File has no known subtitle tracks."); + } + + // Only one track found + else if (tracksFound.Count() == 1) { + trackToRead = tracksFound[0]; + } + + // Pick a track + else { + int choice = wxGetSingleChoiceIndex(_T("Choose which track to read:"),_T("Multiple subtitle tracks found"),tracksNames); + if (choice == -1) { + target->LoadDefault(true); + Close(); + throw _T("Canceled."); + } + trackToRead = tracksFound[choice]; + } + + // Picked track + if (trackToRead != -1) { + // Get codec type (0 = ASS/SSA, 1 = SRT) + trackInfo = mkv_GetTrackInfo(file,trackToRead); + wxString CodecID = wxString(trackInfo->CodecID,*wxConvCurrent); + int codecType = 0; + if (CodecID == _T("S_TEXT/UTF8")) codecType = 1; + + // Read private data if it's ASS/SSA + if (codecType == 0) { + // Read raw data + trackInfo = mkv_GetTrackInfo(file,trackToRead); + unsigned int privSize = trackInfo->CodecPrivateSize; + char *privData = new char[privSize+1]; + memcpy(privData,trackInfo->CodecPrivate,privSize); + privData[privSize] = 0; + wxString privString(privData,wxConvUTF8); + delete privData; + + // Load into file + wxString group = _T("[Script Info]"); + int lasttime = 0; + bool IsSSA = (CodecID == _T("S_TEXT/SSA")); + wxStringTokenizer token(privString,_T("\r\n"),wxTOKEN_STRTOK); + while (token.HasMoreTokens()) { + wxString next = token.GetNextToken(); + if (next[0] == _T('[')) group = next; + lasttime = target->AddLine(next,group,lasttime,IsSSA,&group); + } + + // Insert "[Events]" + //target->AddLine(_T(""),group,lasttime,IsSSA,&group); + //target->AddLine(_T("[Events]"),group,lasttime,IsSSA,&group); + //target->AddLine(_T("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"),group,lasttime,IsSSA,&group); + } + + // Load default if it's SRT + else { + target->LoadDefault(false); + } + + // Read timecode scale + SegmentInfo *segInfo = mkv_GetFileInfo(file); + __int64 timecodeScale = mkv_TruncFloat(trackInfo->TimecodeScale) * segInfo->TimecodeScale; + + // Prepare STD vector to get lines inserted + std::vector subList; + long int order = -1; + + // Progress bar + int totalTime = double(segInfo->Duration) / timecodeScale; + volatile bool canceled = false; + DialogProgress *progress = new DialogProgress(NULL,_("Parsing Matroska"),&canceled,_("Reading subtitles from Matroska file."),0,totalTime); + progress->Show(); + progress->SetProgress(0,1); + + // Load blocks + mkv_SetTrackMask(file, ~(1 << trackToRead)); + while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) { + // Canceled + if (canceled) { + target->LoadDefault(true); + Close(); + throw _T("Canceled"); + } + + // Read to temp + char *tmp = new char[frameSize+1]; + fseek(input->fp, filePos, SEEK_SET); + fread(tmp,1,frameSize,input->fp); + tmp[frameSize] = 0; + wxString blockString(tmp,wxConvUTF8); + + // Get start and end times + //__int64 timecodeScaleLow = timecodeScale / 100; + __int64 timecodeScaleLow = 1000000; + AssTime subStart,subEnd; + subStart.SetMS(startTime / timecodeScaleLow); + subEnd.SetMS(endTime / timecodeScaleLow); + //wxLogMessage(subStart.GetASSFormated() + _T("-") + subEnd.GetASSFormated() + _T(": ") + blockString); + + // Process SSA/ASS + if (codecType == 0) { + // Get order number + int pos = blockString.Find(_T(",")); + wxString orderString = blockString.Left(pos); + orderString.ToLong(&order); + blockString = blockString.Mid(pos+1); + + // Get layer number + pos = blockString.Find(_T(",")); + long int layer = 0; + if (pos) { + wxString layerString = blockString.Left(pos); + layerString.ToLong(&layer); + blockString = blockString.Mid(pos+1); + } + + // Assemble final + blockString = wxString::Format(_T("Dialogue: %i,"),layer) + subStart.GetASSFormated() + _T(",") + subEnd.GetASSFormated() + _T(",") + blockString; + } + + // Process SRT + else { + blockString = wxString(_T("Dialogue: 0,")) + subStart.GetASSFormated() + _T(",") + subEnd.GetASSFormated() + _T(",Default,,0000,0000,0000,,") + blockString; + blockString.Replace(_T("\r\n"),_T("\\N")); + blockString.Replace(_T("\r"),_T("\\N")); + blockString.Replace(_T("\n"),_T("\\N")); + order++; + } + + // Insert into vector + if (subList.size() == order) subList.push_back(blockString); + else { + if ((signed)(subList.size()) < order+1) subList.resize(order+1); + subList[order] = blockString; + } + + // Update progress bar + progress->SetProgress(double(startTime) / 1000000.0,totalTime); + } + + // Insert into file + wxString group = _T("[Events]"); + int lasttime = 0; + bool IsSSA = (CodecID == _T("S_TEXT/SSA")); + for (unsigned int i=0;iAddLine(subList[i],group,lasttime,IsSSA,&group); + } + + // Close progress bar + if (!canceled) progress->Destroy(); + } + + // No track to load + else { + target->LoadDefault(true); + } +} + + diff --git a/core/mkv_wrap.h b/core/mkv_wrap.h index 686145336..217ddab8e 100644 --- a/core/mkv_wrap.h +++ b/core/mkv_wrap.h @@ -47,6 +47,11 @@ #include "vfr.h" +////////////// +// Prototypes +class AssFile; + + ///////////////////////////// // STD IO for MatroskaParser class MkvStdIO : public InputStream { @@ -83,8 +88,6 @@ private: std::vector timecodes; wxArrayInt bytePos; - void Parse(); - public: MkvStdIO *input; MatroskaFile *file; @@ -95,13 +98,15 @@ public: ~MatroskaWrapper(); bool IsOpen() { return file != NULL; } - void Open(wxString filename); + void Open(wxString filename,bool parse=true); void Close(); + void Parse(); void SetToTimecodes(FrameRate &target); wxArrayInt GetBytePositions() { return bytePos; } unsigned int GetFrameCount() { return timecodes.size(); } wxArrayInt GetKeyFrames(); + void GetSubtitles(AssFile *target); static MatroskaWrapper wrapper; }; diff --git a/core/subtitle_format.cpp b/core/subtitle_format.cpp index 1a313e77e..3cc0b020a 100644 --- a/core/subtitle_format.cpp +++ b/core/subtitle_format.cpp @@ -41,6 +41,7 @@ #include "subtitle_format_srt.h" #include "subtitle_format_txt.h" #include "subtitle_format_prs.h" +#include "subtitle_format_mkv.h" #include "ass_file.h" @@ -123,6 +124,7 @@ void SubtitleFormat::LoadFormats () { new ASSSubtitleFormat(); new SRTSubtitleFormat(); new TXTSubtitleFormat(); + new MKVSubtitleFormat(); #ifndef NO_PRS new PRSSubtitleFormat(); #endif diff --git a/core/subtitle_format_mkv.cpp b/core/subtitle_format_mkv.cpp new file mode 100644 index 000000000..f5acbccd7 --- /dev/null +++ b/core/subtitle_format_mkv.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2006, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +/////////// +// Headers +#include "subtitle_format_mkv.h" +#include "ass_dialogue.h" +#include "mkv_wrap.h" +#include "ass_file.h" + + +///////////// +// Can read? +bool MKVSubtitleFormat::CanReadFile(wxString filename) { + return (filename.Right(4).Lower() == _T(".mkv")); +} + + +///////////// +// Read file +void MKVSubtitleFormat::ReadFile(wxString filename,wxString encoding) { + // Open matroska + MatroskaWrapper wrap; + wrap.Open(filename,false); + + // Read subtitles in a temporary object + wrap.GetSubtitles(GetAssFile()); + + // Close matroska + wrap.Close(); +} + + +////////////////////// +// Can write to file? +bool MKVSubtitleFormat::CanWriteFile(wxString filename) { + return false; +} + + +////////////// +// Write file +void MKVSubtitleFormat::WriteFile(wxString _filename,wxString encoding) { +} diff --git a/core/subtitle_format_mkv.h b/core/subtitle_format_mkv.h new file mode 100644 index 000000000..50074f346 --- /dev/null +++ b/core/subtitle_format_mkv.h @@ -0,0 +1,59 @@ +// Copyright (c) 2006, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +/////////// +// Headers +#include "subtitle_format.h" + + +////////////// +// Prototypes +class AssDialogue; + + +///////////////////// +// ASS reader/writer +class MKVSubtitleFormat : public SubtitleFormat { +public: + bool CanReadFile(wxString filename); + void ReadFile(wxString filename,wxString forceEncoding); + + bool CanWriteFile(wxString filename); + void WriteFile(wxString filename,wxString encoding); +};