From: Vásáry Dániel Date: Wed, 13 Jun 2018 21:35:35 +0000 (+0000) Subject: git-tfs-id: [http://tfs.userrendszerhaz.hu:8080/tfs/DefaultCollection]$/MediaCube... X-Git-Url: http://git.useribm.hu/?a=commitdiff_plain;h=f1ba9b6207c9521c0b38158c6b4a59b9ac1f7556;p=mediacube.git git-tfs-id: [tfs.userrendszerhaz.hu:8080/tfs/DefaultCollection]$/MediaCube;C31118 --- diff --git a/client/DxPlay/DxPlay.csproj b/client/DxPlay/DxPlay.csproj index a7959ef6..00defee4 100644 --- a/client/DxPlay/DxPlay.csproj +++ b/client/DxPlay/DxPlay.csproj @@ -172,6 +172,7 @@ + @@ -186,6 +187,7 @@ + Resources.resx diff --git a/client/DxPlay/DxPlayer - Copy.cs b/client/DxPlay/DxPlayer - Copy.cs new file mode 100644 index 00000000..79fed81d --- /dev/null +++ b/client/DxPlay/DxPlayer - Copy.cs @@ -0,0 +1,689 @@ +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Diagnostics; +using System.Windows.Forms; +using System.Threading; + +using DirectShowLib; +using DirectShowLib.Utils; +using Microsoft.Win32.SafeHandles; +using System.ComponentModel; +using System.Drawing.Imaging; +using System.Drawing.Drawing2D; +using System.Collections.Generic; +using Myriadbits.MXF; +using MaestroShared.Metadata; +using NLog; + +namespace DxPlay { + + internal class DxPlayerx : ISampleGrabberCB, IDisposable { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")] + private static extern void CopyMemory(IntPtr Destination, IntPtr Source, [MarshalAs(UnmanagedType.U4)] uint Length); + + private const int MEDIATIME_REFERENCE = 10000000; + + public enum GraphState { + Stopped, + Completed, + Paused, + Playing, + Exiting + } + + public Dictionary stateHunStringValues = new Dictionary(); + + public MediaDescription MediaDescription { get; internal set; } + public Timecode CurrentTC { get; internal set; } + public GraphState State { get; internal set; } + + private IFilterGraph2 m_FilterGraph; + private IMediaControl m_mediaCtrl; + private IMediaEvent m_mediaEvent; + + // Event used by Media Event thread + private ManualResetEvent m_mre; + private BackgroundWorker tcWorker; + // Current state of the graph (can change async) + + public event DxPlayEvent PlayEvent; + public delegate void DxPlayEvent(); + + private Thread m_eventThread = null; + private IMediaSeeking m_mediaSeek = null; + + private IVideoWindow m_videoWindow = null; + private IBaseFilter m_videoRenderer = null; + private object tcLock = new object(); + Bitmap m_Bitmap = null; + public bool IsError { get; set; } +#if DEBUG + // Allow you to "Connect to remote graph" from GraphEdit + DsROTEntry m_DsRot; +#endif + private int m_stride; + + // Release everything. + public void Dispose() { + CloseInterfaces(); + } + + ~DxPlayerx() { + CloseInterfaces(); + } + + Control playerWindow; + private object ppUnk; + + // Play an avi file into a window. Allow for snapshots. + // (Control to show video in, Avi file to play + public DxPlayerx(Control hWin, ref MediaDescription mediaDesc) { + FillTheHunStringvalues(); + State = GraphState.Stopped; + try { + int hr; + IntPtr hEvent; + MediaDescription = mediaDesc; + // Set up the graph + playerWindow = hWin; + + SetupGraph(); + + hWin.Hide(); + hWin.Show(); + + // Get the event handle the graph will use to signal + // when events occur + Debug.WriteLine("GetEventHandle"); + hr = m_mediaEvent.GetEventHandle(out hEvent); + DsError.ThrowExceptionForHR(hr); + + // Wrap the graph event with a ManualResetEvent + m_mre = new ManualResetEvent(false); + m_mre.SafeWaitHandle = new SafeWaitHandle(hEvent, true); + + // Create a new thread to wait for events + Debug.WriteLine("m_eventThread.Start()"); + m_eventThread = new Thread(new ThreadStart(EventWait)); + m_eventThread.Name = "Media Event Thread"; + m_eventThread.Start(); + + m_Bitmap = Properties.Resources.lgs; + + tcWorker = new BackgroundWorker(); + tcWorker.DoWork += TcWorker_DoWork; + tcWorker.WorkerSupportsCancellation = true; + tcWorker.RunWorkerAsync(tcLock); + + } + catch { + Dispose(); + throw; + } + } + + private void FillTheHunStringvalues() { + stateHunStringValues.Add(GraphState.Exiting, StringResource.KILEPES); + stateHunStringValues.Add(GraphState.Paused, StringResource.SZUNETELTETETT); + stateHunStringValues.Add(GraphState.Playing, StringResource.LEJATSZAS); + stateHunStringValues.Add(GraphState.Stopped, StringResource.MEGALLITVA); + stateHunStringValues.Add(GraphState.Completed, StringResource.VEGE); + } + + private void TcWorker_DoWork(object sender, DoWorkEventArgs e) { + while (!e.Cancel) { + UpdateTC(); + Thread.Sleep(10); + } + } + + + // start playing + public void Play() { + // If we aren't already playing (or shutting down) + if (State == GraphState.Completed) + Stop(); + if (State == GraphState.Stopped || State == GraphState.Paused) { + int hr = m_mediaCtrl.Run(); + DsError.ThrowExceptionForHR(hr); + + State = GraphState.Playing; + } + } + + // Pause the capture graph. + public void Pause() { + // If we are playing + if (State == GraphState.Playing) { + int hr = m_mediaCtrl.Pause(); + DsError.ThrowExceptionForHR(hr); + + State = GraphState.Paused; + Seek(CurrentTC.ZeroBasedFrames); + } + } + + // Pause the capture graph. + public void Stop() { + // Can only Stop when playing or paused + if (State == GraphState.Playing || State == GraphState.Paused || State == GraphState.Completed) { + int hr = m_mediaCtrl.Stop(); + DsError.ThrowExceptionForHR(hr); + State = GraphState.Stopped; + } + Rewind(); + PlayEvent?.Invoke(); + } + + // Reset the clip back to the beginning + public void Rewind() { + Seek(0); + } + + public void Seek(int value) { + double frameLength = (double)MEDIATIME_REFERENCE / MediaDescription.FrameRate; + long avgTimePerFrame = (long)Math.Ceiling(MEDIATIME_REFERENCE / MediaDescription.FrameRate); + long requestedPosition = (long)Math.Ceiling(value * frameLength); + int hr = m_mediaSeek.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning); + DsError.ThrowExceptionForHR(hr); + + long currentPosition; + hr = m_mediaSeek.GetCurrentPosition(out currentPosition); + DsError.ThrowExceptionForHR(hr); + bool corrected = false; + int reachedFrames = (int)Math.Abs((double)currentPosition / avgTimePerFrame); + if (reachedFrames != value) { + //NTSC-n nem megy a seek a kerekítési hibák miatt, mindíg ua. a frame jön ki + requestedPosition += (int)frameLength / 2; + hr = m_mediaSeek.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning); + DsError.ThrowExceptionForHR(hr); + corrected = true; + } + + Debug.WriteLine("Seeking requested frame {0} got frame {1}, media position {2}, frame length {3}, corrected {4}", value, reachedFrames, requestedPosition, avgTimePerFrame, corrected); + } + + private void UpdateTC() { + if (m_mediaSeek == null) + return; + long currentPosition; + int hr = m_mediaSeek.GetCurrentPosition(out currentPosition); + DsError.ThrowExceptionForHR(hr); + int frames = ReferenceTimeToFrames(currentPosition); + if (CurrentTC.ZeroBasedFrames != frames) { + CurrentTC.Set(frames); + //Debug.WriteLine("Current frame is {0} ({1}), media position is {2}, AVG frame time is {3}", frames, CurrentTC.ToString(), currentPosition, AvgTimePerFrame); + } + PlayEvent?.Invoke(); + + } + + private int ReferenceTimeToFrames(long refTime) { + long AvgTimePerFrame = (long)Math.Ceiling(MEDIATIME_REFERENCE / MediaDescription.FrameRate); + return (int)Math.Abs((double)refTime / AvgTimePerFrame); + } + + private void SetupGraph() { + int hr; + + try { + IsError = false; + m_FilterGraph = new FilterGraph() as IFilterGraph2; + + IGraphBuilder graphBuilder = m_FilterGraph as IGraphBuilder; + m_mediaSeek = m_FilterGraph as IMediaSeeking; + m_mediaEvent = m_FilterGraph as IMediaEvent; + m_mediaCtrl = m_FilterGraph as IMediaControl; + m_videoWindow = m_FilterGraph as IVideoWindow; + +#if DEBUG + m_DsRot = new DsROTEntry(m_FilterGraph); +#endif + logger.Debug("Add SourceFilter to graph"); + IBaseFilter sourceFilter = null; + hr = m_FilterGraph.AddSourceFilter(MediaDescription.FileName, MediaDescription.FileName, out sourceFilter); + DsError.ThrowExceptionForHR(hr); + + //Type typeFromClsid = Type.GetTypeFromCLSID(new Guid("CCE7BD95-3BC4-4cfb-9664-0BF83201BE09")); + //splitter = (IBaseFilter)Activator.CreateInstance(typeFromClsid); + //m_FilterGraph.AddFilter(splitter, "MXF Splitter"); + //splitter = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "Sony MXF Splitter"); + + logger.Debug("Add LAVSplitter to graph"); + IBaseFilter splitter = LoadSplitter(graphBuilder); + if (splitter == null) + throw new Exception("No splitter!"); + + logger.Debug("Connect SourceFilter -> LAVSplitter"); + FilterGraphTools.ConnectFilters(graphBuilder, sourceFilter, "Output", splitter, "Input", true); + + IAMStreamSelect amStreamSelect = (IAMStreamSelect)splitter; + if (amStreamSelect != null) { + int count = 0; + amStreamSelect.Count(out count); + int audioCount = 0; + for (int i = 0; i < count; i++) { + AMMediaType ppmt; + AMStreamSelectInfoFlags pdwFlags; + int plcid; + int pdwGroup; + string ppszName; + object ppObject; + amStreamSelect.Info(i, out ppmt, out pdwFlags, out plcid, out pdwGroup, out ppszName, out ppObject, out ppUnk); + + if (ppmt.majorType == MediaType.Audio) { + Debug.WriteLine("Found audio channel"); + audioCount++; + } + + DsUtils.FreeAMMediaType(ppmt); + //Marshal.FreeCoTaskMem(ppszName); + if (ppObject != null) + DsUtils.ReleaseComObject(ppUnk); + + } + Debug.WriteLine("Audio count: " + audioCount); + } + + logger.Debug("Add LAVVideo to graph"); + IBaseFilter videoDecoder = LoadVideoDecoder(graphBuilder); + + if (videoDecoder == null) + throw new Exception("No video decoder!"); + + logger.Debug("Connect LAVSplitter -> LAVVideo"); + FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Video", videoDecoder, "Input", true); + + logger.Debug("Add SampleGrabber to graph"); + IBaseFilter sampGrabber = (IBaseFilter)new SampleGrabber(); + ISampleGrabber sampleGrabber = (ISampleGrabber)sampGrabber; + ConfigureSampleGrabber(sampleGrabber); + + hr = m_FilterGraph.AddFilter(sampGrabber, "Sample Grabber"); + DsError.ThrowExceptionForHR(hr); + + logger.Debug("Connect LAVVideo -> SampleGrabber"); + FilterGraphTools.ConnectFilters(graphBuilder, videoDecoder, "Output", sampGrabber, "Input", true); + + AMMediaType media = new AMMediaType(); + sampleGrabber.GetConnectedMediaType(media); + logger.Debug("SaveSizeInfo"); + SaveSizeInfo(media); + DsUtils.FreeAMMediaType(media); + + logger.Debug("Add VideoMixingRenderer9 to graph"); + m_videoRenderer = (IBaseFilter)new VideoMixingRenderer9(); + hr = m_FilterGraph.AddFilter(m_videoRenderer, "Video Mixing Renderer 9"); + DsError.ThrowExceptionForHR(hr); + + + try { + //IPin pin = DsFindPin.ByName(sampGrabber, "Output"); + //m_FilterGraph.RenderEx(pin, AMRenderExFlags.RenderToExistingRenderers, IntPtr.Zero); + //Marshal.ReleaseComObject(pin); + logger.Debug("Connect SampleGrabber -> VideoMixingRenderer9"); + FilterGraphTools.ConnectFilters(graphBuilder, sampGrabber, "Output", m_videoRenderer, "VMR Input0", true); + } + catch (Exception e) { + logger.Error(e); + } + + try { + if (DsFindPin.ByName(splitter, "Audio") != null) { + logger.Debug("Add LAVAudio to graph"); + IBaseFilter audioDecoder = null; + audioDecoder = LoadAudioDecoder(graphBuilder); + if (audioDecoder == null) + throw new Exception("No audio decoder!"); + + logger.Debug("Connect LAVSplitter -> LAVAudio"); + FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Audio", audioDecoder, "Input", true); + FilterGraphTools.RenderPin(graphBuilder, audioDecoder, "Output"); + } else { + logger.Warn("Audio pin not available"); + } + } + catch (Exception ex) { + logger.Warn("Audio pin not available"); + } + + + //logger.Debug("SaveSizeInfo"); + //SaveSizeInfo(sampGrabber as ISampleGrabber); + logger.Debug("SetTimeCodes"); + SetTimeCodes(); + logger.Debug("ConfigureVideoWindow"); + ConfigureVideoWindow(); + + logger.Debug("Enable YADIF deinterlace"); + ILAVVideoSettings settings = (ILAVVideoSettings)videoDecoder; + settings.SetSWDeintMode(LAVSWDeintModes.SWDeintMode_YADIF); + settings.SetSWDeintOutput(LAVDeintOutput.DeintOutput_FramePer2Field); + } + catch (Exception e) { + Debug.WriteLine(e.Message); + IsError = true; + } + finally { + } +#if DEBUG + // Double check to make sure we aren't releasing something + // important. + //GC.Collect(); + //GC.WaitForPendingFinalizers(); +#endif + } + + private static IBaseFilter LoadVideoDecoder(IGraphBuilder graphBuilder) { + IBaseFilter videoDecoder = null; + ILAVVideoSettings lavVideoSettings; + videoDecoder = FilterProvider.GetVideoFilter(out lavVideoSettings); + if (videoDecoder == null) + videoDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Video Decoder"); + else + graphBuilder.AddFilter(videoDecoder, "LAV Video Decoder"); + return videoDecoder; + } + + private static IBaseFilter LoadAudioDecoder(IGraphBuilder graphBuilder) { + IBaseFilter audioDecoder = null; + ILAVAudioSettings lavAudioSettings; + ILAVAudioStatus lavAudioStatus; + audioDecoder = FilterProvider.GetAudioFilter(out lavAudioSettings, out lavAudioStatus); + if (audioDecoder == null) + audioDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Audio Decoder"); + else + graphBuilder.AddFilter(audioDecoder, "LAV Audio Decoder"); + return audioDecoder; + } + + private static IBaseFilter LoadSplitter(IGraphBuilder graphBuilder) { + IBaseFilter splitter = null; + ILAVSplitterSettings lavSplitterSettings; + splitter = FilterProvider.GetSplitter(out lavSplitterSettings); + if (splitter == null) + splitter = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Splitter"); + else + graphBuilder.AddFilter(splitter, "LAV Splitter"); + return splitter; + } + + private void SetTimeCodes() { + int hr; + long duration; + hr = m_mediaSeek.GetDuration(out duration); + DsError.ThrowExceptionForHR(hr); + MediaDescription.duration = new Timecode(); + MediaDescription.Duration.Set(ReferenceTimeToFrames(duration)); + if (MediaDescription.FirstFrame == null) { + try { + MXFFile mxf = new MXFFile(MediaDescription.FileName); + mxf.Inspect(); + //MediaDescription.firstFrame = new Timecode(mxf.FirstSystemItem?.UserDateFullFrameNb, MediaDescription.FrameRate); + MediaDescription.firstFrame = new Timecode((int)mxf.TimecodeComponent.StartTimecode, (float)mxf.TimecodeComponent.RoundedTimecodeBase); + } + catch (Exception ex) { + MediaDescription.firstFrame = new Timecode(); + } + } + //MediaDescription.firstFrame = new Timecode(); + CurrentTC = new Timecode(MediaDescription.FirstFrame); + } + + + // Configure the video window + private void ConfigureVideoWindow() { + int hr; + + // Set the output window + hr = m_videoWindow.put_Owner(playerWindow.Handle); + DsError.ThrowExceptionForHR(hr); + + hr = m_videoWindow.put_MessageDrain(playerWindow.Handle); + DsError.ThrowExceptionForHR(hr); + + // Set the window style + hr = m_videoWindow.put_WindowStyle((WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings)); + DsError.ThrowExceptionForHR(hr); + + // Make the window visible + hr = m_videoWindow.put_Visible(OABool.True); + DsError.ThrowExceptionForHR(hr); + + UpdateVideoWindow(); + } + + public void UpdateVideoWindow() { + Size resolution = MediaDescription.Resolution; + if (resolution.Width == 0 || resolution.Height == 0) + return; + int hr; + // Position the playing location + Rectangle rc = playerWindow.ClientRectangle; + double x = (double)resolution.Width / resolution.Height; + double y = (double)rc.Right / rc.Bottom; + int playerWidth = 0; + int playerHeight = 0; + if (x - y < 0) { + playerWidth = (int)Math.Ceiling(rc.Bottom * x); + playerHeight = rc.Bottom; + } else { + x = (double)resolution.Height / resolution.Width; + playerWidth = rc.Right; + playerHeight = (int)Math.Ceiling(rc.Right * x); ; + } + + hr = m_videoWindow.SetWindowPosition((rc.Right - playerWidth) / 2, (rc.Bottom - playerHeight) / 2, playerWidth, playerHeight); + DsError.ThrowExceptionForHR(hr); + } + + public void ToggleFullscreen() { + m_videoWindow.put_FullScreenMode(IsFullscreen() ? OABool.False : OABool.True); + } + + public bool IsFullscreen() { + OABool isFullscreen; + int hr = m_videoWindow.get_FullScreenMode(out isFullscreen); + DsError.ThrowExceptionForHR(hr); + return isFullscreen == OABool.True ? true : false; + } + + // Set the options on the sample grabber + private void ConfigureSampleGrabber(ISampleGrabber sampGrabber) { + int hr; + //AMMediaType media; + //media = new AMMediaType(); + //media.majorType = MediaType.Video; + //media.subType = MediaSubType.RGB24; + //media.formatType = FormatType.VideoInfo; + //hr = sampGrabber.SetMediaType(media); + //DsError.ThrowExceptionForHR(hr); + //DsUtils.FreeAMMediaType(media); + + hr = sampGrabber.SetCallback(this, 1); + DsError.ThrowExceptionForHR(hr); + + // Configure the samplegrabber + hr = sampGrabber.SetBufferSamples(true); + DsError.ThrowExceptionForHR(hr); + } + + private void SaveSizeInfo(AMMediaType media) { + //int hr; + //AMMediaType media = new AMMediaType(); + //hr = sampGrabber.GetConnectedMediaType(media); + //DsError.ThrowExceptionForHR(hr); + + if ((media.formatType != FormatType.VideoInfo) || (media.formatPtr == IntPtr.Zero)) { + throw new NotSupportedException("Unknown Grabber Media Format"); + } + + // Grab the size info + VideoInfoHeader videoInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(media.formatPtr, typeof(VideoInfoHeader)); + MediaDescription.resolution = new Size(videoInfoHeader.BmiHeader.Width, videoInfoHeader.BmiHeader.Height); + MediaDescription.frameRate = MEDIATIME_REFERENCE / videoInfoHeader.AvgTimePerFrame; + + m_stride = videoInfoHeader.BmiHeader.Width * (videoInfoHeader.BmiHeader.BitCount / 8); + } + + // Shut down capture + private void CloseInterfaces() { + Debug.WriteLine("CloseInterfaces"); + int hr; + GC.SuppressFinalize(this); + if (tcWorker != null) + tcWorker.CancelAsync(); + lock (this) { + if (State != GraphState.Exiting) { + State = GraphState.Exiting; + + // Release the thread (if the thread was started) + if (m_mre != null) { + m_mre.Set(); + } + } + + if (m_mediaCtrl != null) { + // Stop the graph + hr = m_mediaCtrl.Stop(); + FilterGraphTools.DisconnectAllPins((IGraphBuilder)m_mediaCtrl); + FilterGraphTools.RemoveAllFilters((IGraphBuilder)m_mediaCtrl); + m_mediaCtrl = null; + + } + + if (m_videoWindow != null) { + hr = m_videoWindow.put_Visible(OABool.False); + hr = m_videoWindow.put_MessageDrain(IntPtr.Zero); + hr = m_videoWindow.put_Owner(IntPtr.Zero); + m_videoWindow = null; + } + + m_mediaEvent = null; + m_mediaSeek = null; + +#if DEBUG + if (m_DsRot != null) { + m_DsRot.Dispose(); + m_DsRot = null; + } +#endif + if (m_FilterGraph != null) { + Marshal.ReleaseComObject(m_FilterGraph); + m_FilterGraph = null; + } + } + GC.Collect(); + //if (m_eventThread != null) + // m_eventThread.Join(); + } + + public int SampleCB(double SampleTime, IMediaSample pSample) { + Marshal.ReleaseComObject(pSample); + return 0; + } + + public int BufferCB(double SampleTime, IntPtr pBuffer, int BufferLen) { + return 0; + int frames = (int)Math.Abs(SampleTime * MediaDescription.FrameRate); + //Debug.WriteLine("BufferCB frames {0}, sample time {1}", frames, SampleTime); + Font font = new Font("Tahoma", 30); + string display = frames.ToString(); + SizeF size = new SizeF(100, 100); + m_Bitmap = new Bitmap((int)Math.Ceiling(size.Width), (int)Math.Ceiling(size.Height)); + GraphicsUnit units = GraphicsUnit.Point; + RectangleF bitmapRectF = m_Bitmap.GetBounds(ref units); + + Graphics g = Graphics.FromImage(m_Bitmap); + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.FillRectangle(Brushes.Transparent, bitmapRectF); + g.DrawString(display, font, Brushes.White, bitmapRectF); + g.Flush(); + + m_Bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); + Rectangle r = new Rectangle(0, 0, m_Bitmap.Width, m_Bitmap.Height); + lock (this) { + BitmapData bmdLogo = m_Bitmap.LockBits(r, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); + if (bmdLogo != null) { + IntPtr ipSource = bmdLogo.Scan0; + IntPtr ipDest = pBuffer; + + for (int x = 0; x < bmdLogo.Height; x++) { + CopyMemory(ipDest, ipSource, (uint)bmdLogo.Stride); + ipDest = (IntPtr)(ipDest.ToInt64() + m_stride); + ipSource = (IntPtr)(ipSource.ToInt64() + bmdLogo.Stride); + } + } + m_Bitmap.UnlockBits(bmdLogo); + bmdLogo = null; + } + return 0; + } + + // Wait for events to happen. This approach uses waiting on an event handle. + // The nice thing about doing it this way is that you aren't in the windows message + // loop, and don't have to worry about re-entrency or taking too long. Plus, being + // in a class as we are, we don't have access to the message loop. + // Alternately, you can receive your events as windows messages. See + // IMediaEventEx.SetNotifyWindow. + private void EventWait() { + // Returned when GetEvent is called but there are no events + const int E_ABORT = unchecked((int)0x80004004); + + int hr; + IntPtr p1, p2; + EventCode ec; + + do { + // Wait for an event + m_mre.WaitOne(-1, true); + + // Avoid contention for m_State + lock (this) { + // If we are not shutting down + if (State != GraphState.Exiting) { + // Read the event + for ( + hr = m_mediaEvent.GetEvent(out ec, out p1, out p2, 0); + hr >= 0; + hr = m_mediaEvent.GetEvent(out ec, out p1, out p2, 0) + ) { + // Write the event name to the debug window + Debug.WriteLine(ec.ToString()); + + // If the clip is finished playing + if (ec == EventCode.Complete) { + State = GraphState.Completed; + } + + // Release any resources the message allocated + hr = m_mediaEvent.FreeEventParams(ec, p1, p2); + DsError.ThrowExceptionForHR(hr); + + //lock (tcLock) { + // UpdateTC(""); + //} + + } + + // If the error that exited the loop wasn't due to running out of events + if (hr != E_ABORT) { + DsError.ThrowExceptionForHR(hr); + } + } else { + // We are shutting down + Debug.WriteLine("Shutdown"); + break; + } + } + } while (true); + } + + } +} diff --git a/client/DxPlay/DxPlayer.cs b/client/DxPlay/DxPlayer.cs index 9cbe9d8e..061994fc 100644 --- a/client/DxPlay/DxPlayer.cs +++ b/client/DxPlay/DxPlayer.cs @@ -6,7 +6,6 @@ using System.Windows.Forms; using System.Threading; using DirectShowLib; -using DirectShowLib.Utils; using Microsoft.Win32.SafeHandles; using System.ComponentModel; using System.Drawing.Imaging; @@ -35,39 +34,19 @@ namespace DxPlay { } public Dictionary stateHunStringValues = new Dictionary(); - public MediaDescription MediaDescription { get; internal set; } public Timecode CurrentTC { get; internal set; } public GraphState State { get; internal set; } - - private IFilterGraph2 m_FilterGraph; - private IMediaControl m_mediaCtrl; - private IMediaEvent m_mediaEvent; - - // Event used by Media Event thread private ManualResetEvent m_mre; private BackgroundWorker tcWorker; - // Current state of the graph (can change async) - public event DxPlayEvent PlayEvent; public delegate void DxPlayEvent(); - private Thread m_eventThread = null; - private IMediaSeeking m_mediaSeek = null; - private IMediaPosition m_mediaPosition = null; - - private IVideoWindow m_videoWindow = null; - private IBaseFilter m_videoRenderer = null; private object tcLock = new object(); Bitmap m_Bitmap = null; public bool IsError { get; set; } -#if DEBUG - // Allow you to "Connect to remote graph" from GraphEdit - DsROTEntry m_DsRot; -#endif private int m_stride; - // Release everything. public void Dispose() { CloseInterfaces(); } @@ -78,6 +57,7 @@ namespace DxPlay { Control playerWindow; private object ppUnk; + private PlayerGraph graph; // Play an avi file into a window. Allow for snapshots. // (Control to show video in, Avi file to play @@ -99,7 +79,7 @@ namespace DxPlay { // Get the event handle the graph will use to signal // when events occur Debug.WriteLine("GetEventHandle"); - hr = m_mediaEvent.GetEventHandle(out hEvent); + hr = graph.MediaEvent.GetEventHandle(out hEvent); DsError.ThrowExceptionForHR(hr); // Wrap the graph event with a ManualResetEvent @@ -126,6 +106,23 @@ namespace DxPlay { } } + private void SetupGraph() { + graph = new PlayerGraph(MediaDescription.FileName); + + AMMediaType media = new AMMediaType(); + graph.SampleGrabber.GetConnectedMediaType(media); + logger.Debug("SaveSizeInfo"); + SaveSizeInfo(media); + DsUtils.FreeAMMediaType(media); + + logger.Debug("ConfigureSampleGrabber"); + ConfigureSampleGrabber(graph.SampleGrabber); + logger.Debug("SetTimeCodes"); + SetTimeCodes(); + logger.Debug("ConfigureVideoWindow"); + ConfigureVideoWindow(); + } + private void FillTheHunStringvalues() { stateHunStringValues.Add(GraphState.Exiting, StringResource.KILEPES); stateHunStringValues.Add(GraphState.Paused, StringResource.SZUNETELTETETT); @@ -148,7 +145,7 @@ namespace DxPlay { if (State == GraphState.Completed) Stop(); if (State == GraphState.Stopped || State == GraphState.Paused) { - int hr = m_mediaCtrl.Run(); + int hr = graph.MediaControl.Run(); DsError.ThrowExceptionForHR(hr); State = GraphState.Playing; @@ -159,7 +156,7 @@ namespace DxPlay { public void Pause() { // If we are playing if (State == GraphState.Playing) { - int hr = m_mediaCtrl.Pause(); + int hr = graph.MediaControl.Pause(); DsError.ThrowExceptionForHR(hr); State = GraphState.Paused; @@ -171,7 +168,7 @@ namespace DxPlay { public void Stop() { // Can only Stop when playing or paused if (State == GraphState.Playing || State == GraphState.Paused || State == GraphState.Completed) { - int hr = m_mediaCtrl.Stop(); + int hr = graph.MediaControl.Stop(); DsError.ThrowExceptionForHR(hr); State = GraphState.Stopped; } @@ -185,21 +182,24 @@ namespace DxPlay { } public void Seek(int value) { + if (graph == null || graph.MediaSeeking == null) + return; + double frameLength = (double)MEDIATIME_REFERENCE / MediaDescription.FrameRate; long avgTimePerFrame = (long)Math.Ceiling(MEDIATIME_REFERENCE / MediaDescription.FrameRate); long requestedPosition = (long)Math.Ceiling(value * frameLength); - int hr = m_mediaSeek.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning); + int hr = graph.MediaSeeking.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning); DsError.ThrowExceptionForHR(hr); long currentPosition; - hr = m_mediaSeek.GetCurrentPosition(out currentPosition); + hr = graph.MediaSeeking.GetCurrentPosition(out currentPosition); DsError.ThrowExceptionForHR(hr); bool corrected = false; int reachedFrames = (int)Math.Abs((double)currentPosition / avgTimePerFrame); if (reachedFrames != value) { //NTSC-n nem megy a seek a kerekítési hibák miatt, mindíg ua. a frame jön ki requestedPosition += (int)frameLength / 2; - hr = m_mediaSeek.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning); + hr = graph.MediaSeeking.SetPositions(requestedPosition, AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning); DsError.ThrowExceptionForHR(hr); corrected = true; } @@ -208,10 +208,10 @@ namespace DxPlay { } private void UpdateTC() { - if (m_mediaSeek == null) + if (graph == null || graph.MediaSeeking == null) return; long currentPosition; - int hr = m_mediaSeek.GetCurrentPosition(out currentPosition); + int hr = graph.MediaSeeking.GetCurrentPosition(out currentPosition); DsError.ThrowExceptionForHR(hr); int frames = ReferenceTimeToFrames(currentPosition); if (CurrentTC.ZeroBasedFrames != frames) { @@ -227,189 +227,10 @@ namespace DxPlay { return (int)Math.Abs((double)refTime / AvgTimePerFrame); } - private void SetupGraph() { - int hr; - - try { - IsError = false; - m_FilterGraph = new FilterGraph() as IFilterGraph2; - - IGraphBuilder graphBuilder = m_FilterGraph as IGraphBuilder; - m_mediaSeek = m_FilterGraph as IMediaSeeking; - m_mediaPosition = m_FilterGraph as IMediaPosition; - m_mediaEvent = m_FilterGraph as IMediaEvent; - m_mediaCtrl = m_FilterGraph as IMediaControl; - m_videoWindow = m_FilterGraph as IVideoWindow; - -#if DEBUG - m_DsRot = new DsROTEntry(m_FilterGraph); -#endif - logger.Debug("Add SourceFilter to graph"); - IBaseFilter sourceFilter = null; - hr = m_FilterGraph.AddSourceFilter(MediaDescription.FileName, MediaDescription.FileName, out sourceFilter); - DsError.ThrowExceptionForHR(hr); - - //Type typeFromClsid = Type.GetTypeFromCLSID(new Guid("CCE7BD95-3BC4-4cfb-9664-0BF83201BE09")); - //splitter = (IBaseFilter)Activator.CreateInstance(typeFromClsid); - //m_FilterGraph.AddFilter(splitter, "MXF Splitter"); - //splitter = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "Sony MXF Splitter"); - - logger.Debug("Add LAVSplitter to graph"); - IBaseFilter splitter = LoadSplitter(graphBuilder); - if (splitter == null) - throw new Exception("No splitter!"); - - logger.Debug("Connect SourceFilter -> LAVSplitter"); - FilterGraphTools.ConnectFilters(graphBuilder, sourceFilter, "Output", splitter, "Input", true); - - //IAMStreamSelect amStreamSelect = (IAMStreamSelect)splitter; - //if (amStreamSelect != null) { - // int count = 0; - // amStreamSelect.Count(out count); - // int audioCount = 0; - // for (int i = 0; i < count; i++) { - // AMMediaType ppmt; - // AMStreamSelectInfoFlags pdwFlags; - // int plcid; - // int pdwGroup; - // string ppszName; - // object ppObject; - // amStreamSelect.Info(i, out ppmt, out pdwFlags, out plcid, out pdwGroup, out ppszName, out ppObject, out ppUnk); - - // if (ppmt.majorType == MediaType.Audio) { - // Debug.WriteLine("Found audio channel"); - // audioCount++; - // } - - // DsUtils.FreeAMMediaType(ppmt); - // //Marshal.FreeCoTaskMem(ppszName); - // if (ppObject != null) - // DsUtils.ReleaseComObject(ppUnk); - - // } - // Debug.WriteLine("Audio count: " + audioCount); - //} - - logger.Debug("Add LAVVideo to graph"); - IBaseFilter videoDecoder = LoadVideoDecoder(graphBuilder); - - if (videoDecoder == null) - throw new Exception("No video decoder!"); - - logger.Debug("Connect LAVSplitter -> LAVVideo"); - FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Video", videoDecoder, "Input", true); - - logger.Debug("Add SampleGrabber to graph"); - IBaseFilter sampGrabber = (IBaseFilter)new SampleGrabber(); - ConfigureSampleGrabber((ISampleGrabber)sampGrabber); - hr = m_FilterGraph.AddFilter(sampGrabber, "Sample Grabber"); - DsError.ThrowExceptionForHR(hr); - - logger.Debug("Connect LAVVideo -> SampleGrabber"); - FilterGraphTools.ConnectFilters(graphBuilder, videoDecoder, "Output", sampGrabber, "Input", true); - - logger.Debug("Add VideoMixingRenderer9 to graph"); - m_videoRenderer = (IBaseFilter)new VideoMixingRenderer9(); - hr = m_FilterGraph.AddFilter(m_videoRenderer, "Video Mixing Renderer 9"); - DsError.ThrowExceptionForHR(hr); - - //logger.Debug("Add VideoMixingRenderer9 to graph"); - //IVMRDeinterlaceControl9 deinterlace = (IVMRDeinterlaceControl9)m_videoRenderer; - //Guid interlaceMode; - //deinterlace.GetActualDeinterlaceMode(0, out interlaceMode); - - try { - logger.Debug("Connect SampleGrabber -> VideoMixingRenderer9"); - FilterGraphTools.ConnectFilters(graphBuilder, sampGrabber, "Output", m_videoRenderer, "VMR Input0", true); - } - catch (Exception e) { - logger.Error(e); - } - - try { - if (DsFindPin.ByName(splitter, "Audio") != null) { - logger.Debug("Add LAVAudio to graph"); - IBaseFilter audioDecoder = null; - audioDecoder = LoadAudioDecoder(graphBuilder); - if (audioDecoder == null) - throw new Exception("No audio decoder!"); - - logger.Debug("Connect LAVSplitter -> LAVAudio"); - FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Audio", audioDecoder, "Input", true); - FilterGraphTools.RenderPin(graphBuilder, audioDecoder, "Output"); - } else { - logger.Warn("Audio pin not available"); - } - } - catch (Exception ex) { - logger.Warn("Audio pin not available"); - } - - logger.Debug("SaveSizeInfo"); - SaveSizeInfo(sampGrabber as ISampleGrabber); - logger.Debug("SetTimeCodes"); - SetTimeCodes(); - logger.Debug("ConfigureVideoWindow"); - ConfigureVideoWindow(); - - logger.Debug("Enable YADIF deinterlace"); - ILAVVideoSettings settings = (ILAVVideoSettings)videoDecoder; - //settings.SetSWDeintMode(LAVSWDeintModes.SWDeintMode_None); - settings.SetSWDeintMode(LAVSWDeintModes.SWDeintMode_YADIF); - settings.SetSWDeintOutput(LAVDeintOutput.DeintOutput_FramePer2Field); - } - catch (Exception e) { - Debug.WriteLine(e.Message); - IsError = true; - } - finally { - } -#if DEBUG - // Double check to make sure we aren't releasing something - // important. - //GC.Collect(); - //GC.WaitForPendingFinalizers(); -#endif - } - - private static IBaseFilter LoadVideoDecoder(IGraphBuilder graphBuilder) { - IBaseFilter videoDecoder = null; - ILAVVideoSettings lavVideoSettings; - videoDecoder = FilterProvider.GetVideoFilter(out lavVideoSettings); - if (videoDecoder == null) - videoDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Video Decoder"); - else - graphBuilder.AddFilter(videoDecoder, "LAV Video Decoder"); - return videoDecoder; - } - - private static IBaseFilter LoadAudioDecoder(IGraphBuilder graphBuilder) { - IBaseFilter audioDecoder = null; - ILAVAudioSettings lavAudioSettings; - ILAVAudioStatus lavAudioStatus; - audioDecoder = FilterProvider.GetAudioFilter(out lavAudioSettings, out lavAudioStatus); - if (audioDecoder == null) - audioDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Audio Decoder"); - else - graphBuilder.AddFilter(audioDecoder, "LAV Audio Decoder"); - return audioDecoder; - } - - private static IBaseFilter LoadSplitter(IGraphBuilder graphBuilder) { - IBaseFilter splitter = null; - ILAVSplitterSettings lavSplitterSettings; - splitter = FilterProvider.GetSplitter(out lavSplitterSettings); - if (splitter == null) - splitter = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Splitter"); - else - graphBuilder.AddFilter(splitter, "LAV Splitter"); - return splitter; - } - private void SetTimeCodes() { int hr; long duration; - hr = m_mediaSeek.GetDuration(out duration); + hr = graph.MediaSeeking.GetDuration(out duration); DsError.ThrowExceptionForHR(hr); MediaDescription.duration = new Timecode(); MediaDescription.Duration.Set(ReferenceTimeToFrames(duration)); @@ -434,18 +255,18 @@ namespace DxPlay { int hr; // Set the output window - hr = m_videoWindow.put_Owner(playerWindow.Handle); + hr = graph.VideoWindow.put_Owner(playerWindow.Handle); DsError.ThrowExceptionForHR(hr); - hr = m_videoWindow.put_MessageDrain(playerWindow.Handle); + hr = graph.VideoWindow.put_MessageDrain(playerWindow.Handle); DsError.ThrowExceptionForHR(hr); // Set the window style - hr = m_videoWindow.put_WindowStyle((WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings)); + hr = graph.VideoWindow.put_WindowStyle((WindowStyle.Child | WindowStyle.ClipChildren | WindowStyle.ClipSiblings)); DsError.ThrowExceptionForHR(hr); // Make the window visible - hr = m_videoWindow.put_Visible(OABool.True); + hr = graph.VideoWindow.put_Visible(OABool.True); DsError.ThrowExceptionForHR(hr); UpdateVideoWindow(); @@ -471,36 +292,32 @@ namespace DxPlay { playerHeight = (int)Math.Ceiling(rc.Right * x); ; } - hr = m_videoWindow.SetWindowPosition((rc.Right - playerWidth) / 2, (rc.Bottom - playerHeight) / 2, playerWidth, playerHeight); + hr = graph.VideoWindow.SetWindowPosition((rc.Right - playerWidth) / 2, (rc.Bottom - playerHeight) / 2, playerWidth, playerHeight); DsError.ThrowExceptionForHR(hr); } public void ToggleFullscreen() { - m_videoWindow.put_FullScreenMode(IsFullscreen() ? OABool.False : OABool.True); + graph.VideoWindow.put_FullScreenMode(IsFullscreen() ? OABool.False : OABool.True); } public bool IsFullscreen() { OABool isFullscreen; - int hr = m_videoWindow.get_FullScreenMode(out isFullscreen); + int hr = graph.VideoWindow.get_FullScreenMode(out isFullscreen); DsError.ThrowExceptionForHR(hr); return isFullscreen == OABool.True ? true : false; } // Set the options on the sample grabber private void ConfigureSampleGrabber(ISampleGrabber sampGrabber) { - AMMediaType media; int hr; - - // Set the media type to Video/RBG24 - media = new AMMediaType(); - media.majorType = MediaType.Video; - media.subType = MediaSubType.RGB24; - media.formatType = FormatType.VideoInfo; - hr = sampGrabber.SetMediaType(media); - DsError.ThrowExceptionForHR(hr); - - DsUtils.FreeAMMediaType(media); - media = null; + //AMMediaType media; + //media = new AMMediaType(); + //media.majorType = MediaType.Video; + //media.subType = MediaSubType.RGB24; + //media.formatType = FormatType.VideoInfo; + //hr = sampGrabber.SetMediaType(media); + //DsError.ThrowExceptionForHR(hr); + //DsUtils.FreeAMMediaType(media); hr = sampGrabber.SetCallback(this, 1); DsError.ThrowExceptionForHR(hr); @@ -510,13 +327,11 @@ namespace DxPlay { DsError.ThrowExceptionForHR(hr); } - private void SaveSizeInfo(ISampleGrabber sampGrabber) { - int hr; - - // Get the media type from the SampleGrabber - AMMediaType media = new AMMediaType(); - hr = sampGrabber.GetConnectedMediaType(media); - DsError.ThrowExceptionForHR(hr); + private void SaveSizeInfo(AMMediaType media) { + //int hr; + //AMMediaType media = new AMMediaType(); + //hr = sampGrabber.GetConnectedMediaType(media); + //DsError.ThrowExceptionForHR(hr); if ((media.formatType != FormatType.VideoInfo) || (media.formatPtr == IntPtr.Zero)) { throw new NotSupportedException("Unknown Grabber Media Format"); @@ -528,14 +343,11 @@ namespace DxPlay { MediaDescription.frameRate = MEDIATIME_REFERENCE / videoInfoHeader.AvgTimePerFrame; m_stride = videoInfoHeader.BmiHeader.Width * (videoInfoHeader.BmiHeader.BitCount / 8); - DsUtils.FreeAMMediaType(media); - media = null; } // Shut down capture private void CloseInterfaces() { Debug.WriteLine("CloseInterfaces"); - int hr; GC.SuppressFinalize(this); if (tcWorker != null) tcWorker.CancelAsync(); @@ -549,36 +361,8 @@ namespace DxPlay { } } - if (m_mediaCtrl != null) { - // Stop the graph - hr = m_mediaCtrl.Stop(); - FilterGraphTools.DisconnectAllPins((IGraphBuilder)m_mediaCtrl); - FilterGraphTools.RemoveAllFilters((IGraphBuilder)m_mediaCtrl); - m_mediaCtrl = null; - - } - - if (m_videoWindow != null) { - hr = m_videoWindow.put_Visible(OABool.False); - hr = m_videoWindow.put_MessageDrain(IntPtr.Zero); - hr = m_videoWindow.put_Owner(IntPtr.Zero); - m_videoWindow = null; - } - - m_mediaEvent = null; - m_mediaSeek = null; - m_mediaPosition = null; - -#if DEBUG - if (m_DsRot != null) { - m_DsRot.Dispose(); - m_DsRot = null; - } -#endif - if (m_FilterGraph != null) { - Marshal.ReleaseComObject(m_FilterGraph); - m_FilterGraph = null; - } + if (graph != null) + graph.Dispose(); } GC.Collect(); //if (m_eventThread != null) @@ -654,9 +438,9 @@ namespace DxPlay { if (State != GraphState.Exiting) { // Read the event for ( - hr = m_mediaEvent.GetEvent(out ec, out p1, out p2, 0); + hr = graph.MediaEvent.GetEvent(out ec, out p1, out p2, 0); hr >= 0; - hr = m_mediaEvent.GetEvent(out ec, out p1, out p2, 0) + hr = graph.MediaEvent.GetEvent(out ec, out p1, out p2, 0) ) { // Write the event name to the debug window Debug.WriteLine(ec.ToString()); @@ -667,7 +451,7 @@ namespace DxPlay { } // Release any resources the message allocated - hr = m_mediaEvent.FreeEventParams(ec, p1, p2); + hr = graph.MediaEvent.FreeEventParams(ec, p1, p2); DsError.ThrowExceptionForHR(hr); //lock (tcLock) { diff --git a/client/DxPlay/LAVInterfaces.cs b/client/DxPlay/LAVInterfaces.cs index 17362656..86d6937b 100644 --- a/client/DxPlay/LAVInterfaces.cs +++ b/client/DxPlay/LAVInterfaces.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Security; using System.Windows.Forms; +//https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations namespace DxPlay { #region "LAV COM classes" diff --git a/client/DxPlay/PlayerGraph.cs b/client/DxPlay/PlayerGraph.cs new file mode 100644 index 00000000..65276411 --- /dev/null +++ b/client/DxPlay/PlayerGraph.cs @@ -0,0 +1,189 @@ +using DirectShowLib; +using DirectShowLib.Utils; +using NLog; +using System; +using System.Runtime.InteropServices; + +namespace DxPlay { + public class PlayerGraph : FilterGraph, IDisposable { + #if DEBUG + private DsROTEntry m_DsRot; + #endif + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + public IVideoWindow VideoWindow { get; private set; } + public ISampleGrabber SampleGrabber { get; private set; } + public IMediaSeeking MediaSeeking { get; private set; } + public IMediaControl MediaControl { get; private set; } + public IMediaEvent MediaEvent { get; private set; } + + public PlayerGraph(string fileName) { + try { + MediaSeeking = this as IMediaSeeking; + MediaControl = this as IMediaControl; + MediaEvent = this as IMediaEvent; + VideoWindow = this as IVideoWindow; + IGraphBuilder graphBuilder = this as IGraphBuilder; +#if DEBUG + m_DsRot = new DsROTEntry(graphBuilder); +#endif + logger.Debug("Add SourceFilter to graph"); + DsError.ThrowExceptionForHR(graphBuilder.AddSourceFilter(fileName, fileName, out IBaseFilter sourceFilter)); + + IBaseFilter splitter = AddSplitter(graphBuilder, sourceFilter); + IBaseFilter videoDecoder = AddVideoDecoder(graphBuilder, splitter); + IBaseFilter sampleGrabber = AddSampleGrabber(graphBuilder, videoDecoder); + SampleGrabber = (ISampleGrabber)sampleGrabber; + IBaseFilter videoRenderer = AddRenderer(graphBuilder, sampleGrabber); + if (DsFindPin.ByName(splitter, "Audio") != null) { + IBaseFilter audioDecoder = AddAudioDecoder(graphBuilder, splitter); + FilterGraphTools.RenderPin(graphBuilder, audioDecoder, "Output"); + } else { + logger.Warn("Audio pin not available"); + } + + SearchAudioTracks(splitter); + EnableDeinterlace(videoDecoder); + } + catch (Exception e) { + logger.Error(e.Message); + } + } + + private void EnableDeinterlace(IBaseFilter videoDecoder) { + logger.Debug("Enable YADIF deinterlace"); + ILAVVideoSettings settings = (ILAVVideoSettings)videoDecoder; + settings.SetSWDeintMode(LAVSWDeintModes.SWDeintMode_YADIF); + settings.SetSWDeintOutput(LAVDeintOutput.DeintOutput_FramePer2Field); + } + + private IBaseFilter AddAudioDecoder(IGraphBuilder graphBuilder, IBaseFilter splitter) { + logger.Debug("Add LAVAudio to graph"); + IBaseFilter audioDecoder = null; + audioDecoder = LoadAudioDecoder(graphBuilder); + if (audioDecoder == null) + throw new Exception("No audio decoder!"); + + logger.Debug("Connect LAVSplitter -> LAVAudio"); + FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Audio", audioDecoder, "Input", true); + return audioDecoder; + } + + private IBaseFilter AddRenderer(IGraphBuilder graphBuilder, IBaseFilter sampleGrabber) { + logger.Debug("Add VideoMixingRenderer9 to graph"); + IBaseFilter videoRenderer = (IBaseFilter)new VideoMixingRenderer9(); + DsError.ThrowExceptionForHR(graphBuilder.AddFilter(videoRenderer, "Video Mixing Renderer 9")); + + logger.Debug("Connect SampleGrabber -> VideoMixingRenderer9"); + FilterGraphTools.ConnectFilters(graphBuilder, sampleGrabber, "Output", videoRenderer, "VMR Input0", true); + return videoRenderer; + } + + private IBaseFilter AddSampleGrabber(IGraphBuilder graphBuilder, IBaseFilter videoDecoder) { + logger.Debug("Add SampleGrabber to graph"); + IBaseFilter grabber = (IBaseFilter)new SampleGrabber(); + DsError.ThrowExceptionForHR(graphBuilder.AddFilter(grabber, "Sample Grabber")); + logger.Debug("Connect LAVVideo -> SampleGrabber"); + FilterGraphTools.ConnectFilters(graphBuilder, videoDecoder, "Output", grabber, "Input", true); + return grabber; + } + + private IBaseFilter AddVideoDecoder(IGraphBuilder graphBuilder, IBaseFilter splitter) { + logger.Debug("Add LAVVideo to graph"); + IBaseFilter videoDecoder = LoadVideoDecoder(graphBuilder); + + if (videoDecoder == null) + throw new Exception("No video decoder!"); + + logger.Debug("Connect LAVSplitter -> LAVVideo"); + FilterGraphTools.ConnectFilters(graphBuilder, splitter, "Video", videoDecoder, "Input", true); + return videoDecoder; + } + + private IBaseFilter AddSplitter(IGraphBuilder graphBuilder, IBaseFilter sourceFilter) { + logger.Debug("Add LAVSplitter to graph"); + IBaseFilter splitter = LoadSplitter(graphBuilder); + if (splitter == null) + throw new Exception("No splitter!"); + logger.Debug("Connect SourceFilter -> LAVSplitter"); + FilterGraphTools.ConnectFilters(graphBuilder, sourceFilter, "Output", splitter, "Input", true); + return splitter; + } + + private void SearchAudioTracks(IBaseFilter splitter) { + IAMStreamSelect amStreamSelect = (IAMStreamSelect)splitter; + if (amStreamSelect != null) { + int count = 0; + amStreamSelect.Count(out count); + int audioCount = 0; + for (int i = 0; i < count; i++) { + amStreamSelect.Info(i, out AMMediaType ppmt, out AMStreamSelectInfoFlags pdwFlags, out int plcid, out int pdwGroup, out string ppszName, out object ppObject, out object ppUnk); + if (ppmt.majorType == MediaType.Audio) { + logger.Debug("Found audio channel"); + audioCount++; + } + DsUtils.FreeAMMediaType(ppmt); + if (ppObject != null) + DsUtils.ReleaseComObject(ppObject); + if (ppUnk != null) + DsUtils.ReleaseComObject(ppUnk); + } + logger.Debug("Audio count: " + audioCount); + } + } + + private IBaseFilter LoadVideoDecoder(IGraphBuilder graphBuilder) { + IBaseFilter videoDecoder = null; + ILAVVideoSettings lavVideoSettings; + videoDecoder = FilterProvider.GetVideoFilter(out lavVideoSettings); + if (videoDecoder == null) + videoDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Video Decoder"); + else + graphBuilder.AddFilter(videoDecoder, "LAV Video Decoder"); + return videoDecoder; + } + + private IBaseFilter LoadAudioDecoder(IGraphBuilder graphBuilder) { + IBaseFilter audioDecoder = null; + ILAVAudioSettings lavAudioSettings; + ILAVAudioStatus lavAudioStatus; + audioDecoder = FilterProvider.GetAudioFilter(out lavAudioSettings, out lavAudioStatus); + if (audioDecoder == null) + audioDecoder = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Audio Decoder"); + else + graphBuilder.AddFilter(audioDecoder, "LAV Audio Decoder"); + return audioDecoder; + } + + private IBaseFilter LoadSplitter(IGraphBuilder graphBuilder) { + IBaseFilter splitter = null; + ILAVSplitterSettings lavSplitterSettings; + splitter = FilterProvider.GetSplitter(out lavSplitterSettings); + if (splitter == null) + splitter = FilterGraphTools.AddFilterByName(graphBuilder, FilterCategory.LegacyAmFilterCategory, "LAV Splitter"); + else + graphBuilder.AddFilter(splitter, "LAV Splitter"); + return splitter; + } + + // Shut down capture + public void Dispose() { + logger.Debug("CloseInterfaces"); + lock (this) { +#if DEBUG + if (m_DsRot != null) + m_DsRot.Dispose(); +#endif + IMediaControl mediaCtrl = (IMediaControl) this; + if (mediaCtrl != null) { + mediaCtrl.Stop(); + IGraphBuilder graphBuilder = (IGraphBuilder)mediaCtrl; + FilterGraphTools.DisconnectAllPins(graphBuilder); + FilterGraphTools.RemoveAllFilters(graphBuilder); + } + + Marshal.ReleaseComObject(this); + } + } + + } +} diff --git a/server/-configuration/scheduledjobs.json b/server/-configuration/scheduledjobs.json index 011df72e..bbf85d24 100644 --- a/server/-configuration/scheduledjobs.json +++ b/server/-configuration/scheduledjobs.json @@ -3,7 +3,7 @@ "active": false, "executeimmediate": false, "name" : "SYS: recreate-lowres", - "template": "recreate-lowres.xml", + "template": "sys-recreate-lowres.xml", "parameters": [ {"name": "filePath", "value": "c:/_downloads/Silicon.Valley.S04E08.HDTV.x264.HUN-SFY/Silicon.Valley.S04E08.HDTV.x264.HUN-SFY.mkv", "type": "java.lang.String" } ] diff --git a/server/user.jobengine.executors/config/config.xml b/server/user.jobengine.executors/config/config.xml index d4677b2f..0193802c 100644 --- a/server/user.jobengine.executors/config/config.xml +++ b/server/user.jobengine.executors/config/config.xml @@ -3,6 +3,7 @@ + @@ -26,5 +27,4 @@ - \ No newline at end of file diff --git a/server/user.jobengine.executors/config/scheduledjobs.json b/server/user.jobengine.executors/config/scheduledjobs.json index f4e0d2ac..1574ddb3 100644 --- a/server/user.jobengine.executors/config/scheduledjobs.json +++ b/server/user.jobengine.executors/config/scheduledjobs.json @@ -1,4 +1,11 @@ {"joblist":[ + { + "name" : "sys: Check LOWRES integrity", + "template": "check-lowres-integrity.xml", + "parameters": [ + {"name": "webPath", "value": "/mediacube/data/lowres/www/video", "type": "java.lang.String"} + ] + }, { "active": false, "executeimmediate": false, diff --git a/server/user.jobengine.executors/jobtemplates/sys-check-lowres-integrity.xml b/server/user.jobengine.executors/jobtemplates/sys-check-lowres-integrity.xml new file mode 100644 index 00000000..4c5e1811 --- /dev/null +++ b/server/user.jobengine.executors/jobtemplates/sys-check-lowres-integrity.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/user.jobengine.executors/jobtemplates/sys-recreate-length.xml b/server/user.jobengine.executors/jobtemplates/sys-recreate-length.xml new file mode 100644 index 00000000..44c0e099 --- /dev/null +++ b/server/user.jobengine.executors/jobtemplates/sys-recreate-length.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/user.jobengine.executors/jobtemplates/recreate-lowres.xml b/server/user.jobengine.executors/jobtemplates/sys-recreate-lowres.xml similarity index 100% rename from server/user.jobengine.executors/jobtemplates/recreate-lowres.xml rename to server/user.jobengine.executors/jobtemplates/sys-recreate-lowres.xml diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/CheckLOWRESIntegrity.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/CheckLOWRESIntegrity.java new file mode 100644 index 00000000..5cc81690 --- /dev/null +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/CheckLOWRESIntegrity.java @@ -0,0 +1,66 @@ +package user.jobengine.server.steps; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; + +import user.jobengine.db.IItemManager; +import user.jobengine.db.IResultSetConsumer; +import user.jobengine.db.Media; +import user.jobengine.db.MediaFile; +import user.jobengine.server.IJobEngine; +import user.jobengine.server.IJobRuntime; + +public class CheckLOWRESIntegrity extends JobStep { + private static final Logger logger = LogManager.getLogger(); + private IItemManager manager; + private Marker marker; + private String webPath; + + private void checkIntegrity(MediaFile mediaFile) { + Path path = Paths.get(webPath, mediaFile.getRelativePath()); + if (!path.toFile().exists()) { + logger.warn(marker, "{} {}", mediaFile.getId(), path); + } + } + + @StepEntry + public Object[] execute(String webPath, IJobEngine jobEngine, IJobRuntime jobRuntime) throws Exception { + this.webPath = webPath; + marker = jobRuntime.getMarker(); + manager = jobEngine.getItemManager(); + List mediaIdentities = getTranscodedMediaIdentities(); + int allCount = mediaIdentities.size(); + int currentCount = 0; + for (long id : mediaIdentities) { + Media media = manager.getMedia(id); + List mediaFiles = media.getMediaFiles(); + for (MediaFile mediaFile : mediaFiles) { + if (mediaFile.getStoreId() == 21) + checkIntegrity(mediaFile); + } + currentCount++; + jobRuntime.incrementProgress(currentCount * 100 / allCount); + } + return null; + } + + public List getTranscodedMediaIdentities() { + final List result = new ArrayList<>(); + String query = "select mediaid from vw_mediafiles where mediafilecount = 2"; + + IResultSetConsumer consumer = rs -> { + result.add(rs.getLong("mediaId")); + return true; + }; + + manager.executeQuery(query, consumer, null); + return result; + } + +} diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/CopyForArchiveNEXIOMaterialsStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/CopyForArchiveNEXIOMaterialsStep.java index 5627362f..d1643585 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/CopyForArchiveNEXIOMaterialsStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/CopyForArchiveNEXIOMaterialsStep.java @@ -13,6 +13,7 @@ import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPReply; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; import com.ibm.nosql.json.api.BasicDBObject; import com.ibm.nosql.json.api.DB; @@ -56,10 +57,11 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { private StoreUri targetUri; private int nexioKillDateDays; private String nexioAgency; + private Marker systemMarker; private int check(int value, String name) { if (value == 0) { - logger.error(getMarker(), "A folyamat '{}' bemeneti paramétere 0.", name); + logger.error(systemMarker, "A folyamat '{}' bemeneti paramétere 0.", name); throw new NullPointerException(String.format("System is not configured properly, missing '%s' input parameter.", name)); } return value; @@ -67,7 +69,7 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { private String check(String value, String name) { if (value == null) { - logger.error(getMarker(), "A folyamat '{}' bemeneti paramétere üres.", name); + logger.error(systemMarker, "A folyamat '{}' bemeneti paramétere üres.", name); throw new NullPointerException(String.format("System is not configured properly, missing '%s' input parameter.", name)); } return value; @@ -79,7 +81,7 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { try { copyFile(fileArchive, rundownArchive, storyArchive); } catch (Exception e) { - logger.error(getMarker(), "A '{}' clip archiválása sikertelen. A rendszer üzenete: {}", fileArchive.getFileName(), e.getMessage()); + logger.error(systemMarker, "A '{}' clip archiválása sikertelen. A rendszer üzenete: {}", fileArchive.getFileName(), e.getMessage()); } } } @@ -144,13 +146,14 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { @StepEntry public Object[] execute(int nexioPort, String nexioUserName, String nexioPassword, String archiveFtp, String archiveUserName, String archivePassword, int daysBeforeNow, int nexioKillDateDays, String nexioAgency, IJobEngine jobEngine, IJobRuntime jobRuntime) throws Exception { + systemMarker = jobRuntime.getMarker(); setAndCheck(nexioPort, nexioUserName, nexioPassword, archiveFtp, archiveUserName, archivePassword, nexioKillDateDays, nexioAgency, jobEngine); octopusAPI = new OctopusAPI(); Calendar scheduledDate = Calendar.getInstance(); scheduledDate.add(Calendar.DAY_OF_YEAR, -1 * daysBeforeNow); List rundowns = octopusAPI.getRundowns(scheduledDate.getTime()); if (rundowns == null) { - logger.warn(getMarker(), "Nem található adástükör a {} napra.", CalendarUtils.toDateString(scheduledDate)); + logger.warn(systemMarker, "Nem található adástükör a {} napra.", CalendarUtils.toDateString(scheduledDate)); return null; } @@ -264,7 +267,7 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { db.getCollection(ARCHIVEDRUNDOWNS).save(currentRundownID); } catch (Exception e) { logger.catching(e); - logger.error(getMarker(), + logger.error(systemMarker, String.format("A %s %s tükör archiválása nem lehetséges, mert a annak ellenőrzése hibát jelzett. A rendszer üzenete: %s", rundownID, rundownName, e.getMessage())); } @@ -315,22 +318,22 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { int nexioKillDateDays, String nexioAgency, IJobEngine jobEngine) throws Exception { db = NoSQLUtils.getNoSQLDB(); if (db == null) { - logger.error(getMarker(), "Az NoSQL adatkezelő réteg nem elérhető."); + logger.error(systemMarker, "Az NoSQL adatkezelő réteg nem elérhető."); throw new NullPointerException("Internal error, missing NoSQL DB reference."); } if (jobEngine == null) { - logger.error(getMarker(), "Az folyamatkezelő réteg nem elérhető."); + logger.error(systemMarker, "Az folyamatkezelő réteg nem elérhető."); throw new NullPointerException("Internal error, missing JobEngine reference."); } manager = jobEngine.getItemManager(); if (manager == null) { - logger.error(getMarker(), "Az adatbáziskezelő réteg nem elérhető."); + logger.error(systemMarker, "Az adatbáziskezelő réteg nem elérhető."); throw new NullPointerException("Internal error, missing ItemManager reference."); } String nexioHost = System.getProperty("nexio.host"); if (StringUtils.isBlank(nexioHost)) { - logger.error(getMarker(), "A 'nexio.host' rendszer paraméter nem található."); + logger.error(systemMarker, "A 'nexio.host' rendszer paraméter nem található."); throw new NullPointerException("System is not configured properly, 'jobengine.selenio.address' startup parameter missing."); } check(nexioPort, "nexioPort"); @@ -347,7 +350,7 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { sourceUri.setUserName(nexioUserName); sourceUri.setPassword(nexioPassword); if (sourceUri == null) { - logger.error(getMarker(), "A forrás nem elérhető."); + logger.error(systemMarker, "A forrás nem elérhető."); throw new NullPointerException("Internal error, missing 'sourceUri'."); } @@ -359,7 +362,7 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { targetUri.setUserName(archiveUserName); targetUri.setPassword(archivePassword); if (targetUri == null) { - logger.error(getMarker(), "A cél nem elérhető."); + logger.error(systemMarker, "A cél nem elérhető."); throw new NullPointerException("Internal error, missing 'targetUri'."); } @@ -369,6 +372,7 @@ public class CopyForArchiveNEXIOMaterialsStep extends JobStep { logger.info("Transfer chunk {}", fileName); OutputStream outStream = null; try { + targetFtp = ((FtpDirectoryLister) targetUri.getLister()).connect(); outStream = targetFtp.storeFileStream(fileName + MXFEXT); if (outStream == null) { throw new NullPointerException("Can not open: " + fileName + MXFEXT + " Reply:" + targetFtp.getReplyString()); diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/CreateMissingLowresStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/CreateMissingLowresStep.java index 76abc690..cc7ce391 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/CreateMissingLowresStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/CreateMissingLowresStep.java @@ -39,6 +39,8 @@ public class CreateMissingLowresStep extends JobStep { for (Media media : medias) { String name = media.getMediaFilesName(); + if (name == null) + continue; DBObject existing = collection.findOne(new BasicDBObject("name", name)); if (existing != null) continue; diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMBackupStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMBackupStep.java index 20592bec..d89aca15 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMBackupStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/TSMBackupStep.java @@ -29,6 +29,7 @@ import user.jobengine.server.IJobRuntime; import user.jobengine.server.JobEngineException; public class TSMBackupStep extends JobStep { + private static final String MXFEXT = ".MXF"; private static final Logger logger = LogManager.getLogger(); private IItemManager manager; private File sourceMediaFile; @@ -50,7 +51,12 @@ public class TSMBackupStep extends JobStep { jobRuntime.setDescription(String.format("%s: %s", jobRuntime.getDescription(), details)); - if (archiveItem.getExistingMediaId() == 0) { + //A dupla ellenorzes a napon beluli ismetlesek miatt kell + long existingMediaId = archiveItem.getExistingMediaId(); + if (existingMediaId == 0) + existingMediaId = manager.getExistingRundownMedia(sourceFileName.replace(MXFEXT, "")); + + if (existingMediaId == 0) { StoreUri sourceUri = manager.createStoreUri(RemoteStoreProtocol.LOCAL, sourceMediaFile.getParent().toString()); final IJobRuntime runtime = jobRuntime; @@ -70,7 +76,7 @@ public class TSMBackupStep extends JobStep { } else { logger.info(marker, "Az '{}' TSM mentése nem szükséges, mert már megtalálható az archívumban.", sourceFileName); } - saveMetadata(mediaCubeMedia, sourceFileName, archiveItem); + saveMetadata(mediaCubeMedia, sourceFileName, existingMediaId); logger.info(marker, "Az '{}' archiválása sikeres.", sourceFileName); if (killDateDays > 0) @@ -88,13 +94,14 @@ public class TSMBackupStep extends JobStep { return null; } - private void saveMetadata(Media mediaCubeMedia, String sourceFileName, ArchiveItem archiveItem) { - if (archiveItem.getExistingMediaId() == 0) { + private void saveMetadata(Media mediaCubeMedia, String sourceFileName, long existingMediaId) { + + if (existingMediaId == 0) { MediaFile mediaFile = manager.createMediaFile(sourceFileName, fileType, tsmStore, mediaCubeMedia); mediaFile.setHouseId(sourceFileName); mediaFile.add(); } else { - Media existingMedia = manager.getMedia(archiveItem.getExistingMediaId()); + Media existingMedia = manager.getMedia(existingMediaId); List mediaFiles = existingMedia.getMediaFiles(); if (mediaFiles != null) { for (MediaFile mf : mediaFiles) { diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeFFAStranStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeFFAStranStep.java index 4a95df9d..9b98c4bb 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeFFAStranStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeFFAStranStep.java @@ -53,16 +53,21 @@ public class TranscodeFFAStranStep extends JobStep { String details = String.format("%s (%d bytes)", sourceFileName, sourceMediaFile.length()); Path targetPath = null; try { - String sourceFile = Paths.get(globalHiresSourcePath, sourceFileName).toString(); - IFFAStransAPI api = new FFAStransAPI(transcoderAddress, p -> { - if (p <= 100) - jobRuntime.incrementProgress(p); - }); - api.submit(transcoderTemplateName, sourceFile); - jobRuntime.setDescription(String.format("%s: %s", jobRuntime.getDescription(), details)); - api.monitor(1000); targetPath = Paths.get(localLowresTargetPath, sourceFileName.replace(MXFEXT, MP4EXT)); + if (!targetPath.toFile().exists()) { + jobRuntime.setDescription(String.format("%s: %s", jobRuntime.getDescription(), details)); + String sourceFile = Paths.get(globalHiresSourcePath, sourceFileName).toString(); + IFFAStransAPI api = new FFAStransAPI(transcoderAddress, p -> { + if (p <= 100) + jobRuntime.incrementProgress(p); + }); + + api.submit(transcoderTemplateName, sourceFile); + api.monitor(1000); + } + postprocess(targetPath, webPath); + } catch (Exception e) { logger.catching(e); Message m = new ParameterizedMessage("Az '{}' állomány átkódolása sikertelen. A rendszer hibaüzenete: {}", details, e.getMessage()); @@ -89,17 +94,26 @@ public class TranscodeFFAStranStep extends JobStep { Path lowresPath = null; try { String transcodedFileName = transcodedFilePath.getFileName().toString(); + String targetPath = null; if (transcodedFileName.indexOf(".") > 2) { Path subdir = Paths.get(transcodedFileName.substring(0, 1), transcodedFileName.substring(1, 2), transcodedFileName.substring(2, 3)); - manager.createMediaFile(Paths.get(subdir.toString(), transcodedFileName).toString(), fileType, store, mediaCubeMedia).add(); EscortFiles.ensureUNCFolder(webPath, subdir.toString()); - lowresPath = Paths.get(webPath, subdir.toString(), transcodedFileName); - Files.move(transcodedFilePath, lowresPath); + targetPath = Paths.get(subdir.toString(), transcodedFileName).toString(); } else { - manager.createMediaFile(transcodedFileName, fileType, store, mediaCubeMedia).add(); - lowresPath = Paths.get(webPath, transcodedFileName); - Files.move(transcodedFilePath, lowresPath); + targetPath = transcodedFileName; + } + lowresPath = Paths.get(webPath, targetPath); + int version = 1; + while (lowresPath.toFile().exists()) { + String fileName = transcodedFileName + version + MP4EXT; + lowresPath = Paths.get(lowresPath.toString().replace(transcodedFileName, fileName)); + targetPath = targetPath.replace(transcodedFileName, fileName); + transcodedFileName = fileName; + version++; } + + Files.move(transcodedFilePath, lowresPath); + manager.createMediaFile(targetPath, fileType, store, mediaCubeMedia).add(); } catch (IOException e) { logger.catching(e); logger.error(marker, "A '{}' állomány mozgatása a '{}' helyre nem sikerült.", transcodedFilePath, lowresPath); diff --git a/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeSELENIOStep.java b/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeSELENIOStep.java index 51f6bacd..7f1f21b2 100644 --- a/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeSELENIOStep.java +++ b/server/user.jobengine.executors/src/user/jobengine/server/steps/TranscodeSELENIOStep.java @@ -43,6 +43,7 @@ import user.jobengine.server.IJobRuntime; public class TranscodeSELENIOStep extends JobStep { + private static final String MXFEXT = ".MXF"; private static final String LOWRES_FILETYPE = "Low-res"; private static final Logger logger = LogManager.getLogger(); private final List showStoppers = Arrays.asList(State.CANCELLED, State.COMPLETE, State.FAILED); @@ -94,15 +95,20 @@ public class TranscodeSELENIOStep extends JobStep { marker = jobRuntime.getMarker(); String sourceFileName = null; - //Nincs mit transzkódolni - if (archiveItem.getExistingMediaId() != 0) - return null; - try { setAndCheck(globalSourcePath, transcoderTargetPath, webPath, jobEngine); File sourceMediaFile = new File(archiveItem.getMediaFile()); sourceFileName = sourceMediaFile.getName(); + + //Nincs mit transzkódolni, a TSMBackupStep csinal masolatot a mediafileokrol + //A dupla ellenorzes a napon beluli ismetlesek miatt kell + long existingMediaId = archiveItem.getExistingMediaId(); + if (existingMediaId == 0) + existingMediaId = manager.getExistingRundownMedia(sourceFileName.replace(MXFEXT, "")); + if (existingMediaId != 0) + return null; + String details = String.format("%s (%d bytes)", sourceFileName, sourceMediaFile.length()); Path inputPath = Paths.get(globalSourcePath, sourceFileName); diff --git a/server/user.jobengine.osgi.commons/src/user/commons/FFAStransAPI.java b/server/user.jobengine.osgi.commons/src/user/commons/FFAStransAPI.java index 55c4df67..cabb330e 100644 --- a/server/user.jobengine.osgi.commons/src/user/commons/FFAStransAPI.java +++ b/server/user.jobengine.osgi.commons/src/user/commons/FFAStransAPI.java @@ -52,12 +52,28 @@ public class FFAStransAPI implements IFFAStransAPI { private ResteasyWebTarget webTarget; private IProgressChangedListener listener; private String jobId; + private BasicDBObject lastJobToSubmit; public FFAStransAPI(String apiAddress, IProgressChangedListener listener) { this.listener = listener; webTarget = new ResteasyClientBuilder().build().target(apiAddress); } + private void doSubmit() throws Exception { + ResteasyWebTarget target = webTarget.path("jobs"); + Response apiResponse = target.request().post(Entity.entity(lastJobToSubmit.toString(), MediaType.APPLICATION_JSON)); + if (apiResponse.getStatus() != 202) + throw new Exception("Can not submit, response status is: " + apiResponse.getStatus()); + String json = apiResponse.readEntity(String.class); + logger.info("Transoder response: {}", json); + if (StringUtils.isBlank(json)) + throw new Exception("Can not submit, response JSON is empty"); + BasicDBObject resultObject = (BasicDBObject) JSONUtil.jsonToDbObject(json); + if (resultObject == null) + throw new Exception("Can not submit, response object is null"); + jobId = resultObject.getString("job_id"); + } + @Override public BasicDBObject getHistory(String jobStart) { ResteasyWebTarget target = webTarget.path("history"); @@ -168,6 +184,15 @@ public class FFAStransAPI implements IFFAStransAPI { //System.out.println("Progress: " + 100); listener.onProgressChanged(100); BasicDBObject history = getHistory(jobStart); + + if (history == null && lastJobToSubmit != null) { + //plusz 1 proba + doSubmit(); + lastJobToSubmit = null; + monitor(pollIntervall); + return; + } + //System.out.println("History: " + history.toPrettyString(null)); if (history == null || NoSQLUtils.asLong(history, "state") != 1) { String error = NoSQLUtils.asString(history, "outcome"); @@ -194,18 +219,7 @@ public class FFAStransAPI implements IFFAStransAPI { if (wfID < 0) throw new Exception("Workflow not exists: " + workflowName); - BasicDBObject job = new BasicDBObject("wf_id", wfID).append("inputfile", inputFile); - ResteasyWebTarget target = webTarget.path("jobs"); - Response apiResponse = target.request().post(Entity.entity(job.toString(), MediaType.APPLICATION_JSON)); - if (apiResponse.getStatus() != 202) - throw new Exception("Can not submit, response status is: " + apiResponse.getStatus()); - String json = apiResponse.readEntity(String.class); - logger.info("Transoder response: {}", json); - if (StringUtils.isBlank(json)) - throw new Exception("Can not submit, response JSON is empty"); - BasicDBObject resultObject = (BasicDBObject) JSONUtil.jsonToDbObject(json); - if (resultObject == null) - throw new Exception("Can not submit, response object is null"); - jobId = resultObject.getString("job_id"); + lastJobToSubmit = new BasicDBObject("wf_id", wfID).append("inputfile", inputFile); + doSubmit(); } } diff --git a/server/user.jobengine.osgi.db/sql/cleanup.sql b/server/user.jobengine.osgi.db/sql/cleanup.sql index 0a7be31c..5a6e698d 100644 --- a/server/user.jobengine.osgi.db/sql/cleanup.sql +++ b/server/user.jobengine.osgi.db/sql/cleanup.sql @@ -1,5 +1,14 @@ ---MEDIAFILE -select mediaid from vw_mediafiles where mediafilecount = 1 +--Hamis LOWRES +drop view vw_mediafiles_path +create view vw_mediafiles_path as +select relativepath, count(*) as count from mediafile where storeid=21 group by relativepath + +select * from mediafile mf, vw_mediafiles_path v where v.count > 1 and mf.RELATIVEPATH = v.RELATIVEPATH order by v.count, mf.relativepath, mf.id + +--MEDIAFILE +select count(*) from vw_mediafiles where mediafilecount = 0 + +select * from mediafile where relativepath = '' select * from mediafile f where f.mediaid in (select mediaid from vw_mediafiles where mediafilecount = 1) --delete from mediafile f where f.mediaid in (select mediaid from vw_mediafiles where mediafilecount = 1) diff --git a/server/user.jobengine.osgi.server/WEB-INF/web.xml b/server/user.jobengine.osgi.server/WEB-INF/web.xml index 992b6297..b0826fdd 100644 --- a/server/user.jobengine.osgi.server/WEB-INF/web.xml +++ b/server/user.jobengine.osgi.server/WEB-INF/web.xml @@ -55,8 +55,10 @@ - 15 + + 60 + index.html index.htm @@ -68,4 +70,14 @@ index.zul index.zhtml + + + + Everything on the app + /* + + + CONFIDENTIAL + + diff --git a/server/user.jobengine.osgi.server/WEB-INF/zk.xml b/server/user.jobengine.osgi.server/WEB-INF/zk.xml index ae2934e9..8afaaab8 100644 --- a/server/user.jobengine.osgi.server/WEB-INF/zk.xml +++ b/server/user.jobengine.osgi.server/WEB-INF/zk.xml @@ -10,6 +10,7 @@ Lejárt a munkamenet, kérem töltse be újra az alkalmazást. + diff --git a/server/user.jobengine.osgi.server/resources/i3-label_hu.properties b/server/user.jobengine.osgi.server/resources/i3-label_hu.properties index d850041d..4d20de8d 100644 --- a/server/user.jobengine.osgi.server/resources/i3-label_hu.properties +++ b/server/user.jobengine.osgi.server/resources/i3-label_hu.properties @@ -1,4 +1,4 @@ -version=2.3.7 +version=2.3.8 footer=2016 © Copyright User Rendszerház Kft. login_info=Információ diff --git a/server/user.jobengine.osgi.server/test/user/jobengine/server/IT/Support.java b/server/user.jobengine.osgi.server/test/user/jobengine/server/IT/Support.java index 17b95075..0afd81ff 100644 --- a/server/user.jobengine.osgi.server/test/user/jobengine/server/IT/Support.java +++ b/server/user.jobengine.osgi.server/test/user/jobengine/server/IT/Support.java @@ -1,6 +1,7 @@ package user.jobengine.server.IT; import java.io.File; +import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; @@ -15,12 +16,16 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Stream; +import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.DateUtils; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -33,8 +38,10 @@ import com.ibm.nosql.json.api.DBCollection; import sqlj.runtime.ref.DefaultContext; import user.commons.CalendarUtils; import user.commons.IEntityBase; +import user.commons.ListUtils; import user.commons.logging.LogUtils; import user.commons.nosql.NoSQLUtils; +import user.commons.octopus.IOctopusAPI; import user.commons.octopus.OctopusAPI; import user.jobengine.db.IItemManager; import user.jobengine.db.IResultSetConsumer; @@ -61,9 +68,12 @@ public class Support { @BeforeClass static public void setUpConnection() { - System.setProperty("jobengine.octopus.rundowns.name", "test_rundowns"); - System.setProperty("jobengine.octopus.stories.name", "test_stories"); - System.setProperty("jobengine.octopus.storyfolders.name", "test_storyfolders"); + // System.setProperty("jobengine.octopus.rundowns.name", "test_rundowns"); + // System.setProperty("jobengine.octopus.stories.name", "test_stories"); + // System.setProperty("jobengine.octopus.storyfolders.name", "test_storyfolders"); + System.setProperty("jobengine.octopus.rundowns.name", "rundowns180608"); + System.setProperty("jobengine.octopus.stories.name", "stories180608"); + System.setProperty("jobengine.octopus.storyfolders.name", "storyfolders180608"); System.setProperty("jobengine.nosql.db.url", "jdbc:db2://10.10.1.27:50000/mc:retrieveMessagesFromServerOnGetMessage=true;"); System.setProperty("jobengine.nosql.db.user", "db2admin"); @@ -212,6 +222,61 @@ public class Support { return result; } + public Map> getExistingItemHouseIDs(String houseid) { + final Map> result = new HashMap<>(); + final List resultList = new ArrayList<>(); + StringBuilder query = new StringBuilder(); + query.append("select itemid, mediaid, replace(mediafilehouseid, concat('-', concat(itemhouseid,'.MXF')), '') filename"); + query.append(" "); + query.append("from vw_items where replace(mediafilehouseid, concat('-', itemhouseid), '') != mediafilehouseid"); + query.append(" "); + query.append("and replace(mediafilehouseid, concat('-', concat(itemhouseid,'.MXF')), '') = ?"); + IStatementDecorator decorator = st -> { + st.setString(1, houseid); + }; + IResultSetConsumer consumer = rs -> { + Media media = manager.getMedia(rs.getLong("mediaid")); + + if (result.size() == 0) + result.put(media, resultList); + + Item item = manager.getItem(media.getItemId()); + resultList.add(item.getTitle()); + return true; + }; + manager.executeQuery(query.toString(), consumer, decorator); + return result; + } + + private Item getRundownItem(BasicDBObject rundown) { + + StringBuilder query = new StringBuilder(); + query.append("select id from item where title=?"); + IStatementDecorator decorator = st -> { + st.setString(1, getRundownTitle(rundown)); + }; + Item[] result = { null }; + IResultSetConsumer consumer = rs -> { + Item item = manager.getItem(rs.getLong("id")); + result[0] = item; + return false; + }; + manager.executeQuery(query.toString(), consumer, decorator); + return result[0]; + } + + private String getRundownTitle(BasicDBObject rundown) { + String name = NoSQLUtils.asString(NoSQLUtils.asDBObject(rundown, IOctopusAPI.RUNDOWN_TYPE), IOctopusAPI.NAME); + if (StringUtils.isBlank(name)) + return null; + String channel = NoSQLUtils.asString(NoSQLUtils.asDBObject(rundown, IOctopusAPI.CHANNEL), IOctopusAPI.NAME); + Date scheduledStart = rundown.getDate(IOctopusAPI.SCHEDULED_START); + if (scheduledStart == null) + return null; + String start = CalendarUtils.toString(CalendarUtils.createCalendar(scheduledStart), "yyyy.MM.dd HH:mm"); + return String.format("%s %s %s", start, name, channel); + } + private void processLowresDuplicateGroup(String fileName) { System.out.println("*** Processing: " + fileName); String query = "select mediafileid, mediafilehouseid, relativepath from vw_items_rd_lh where filename = ?"; @@ -326,4 +391,83 @@ public class Support { return result; } + @Test + public void repairRundownChunks() throws Exception { + OctopusAPI octopusAPI = new OctopusAPI(); + Path sourcePath = Paths.get("c:\\Temp\\__"); + final Set names = new LinkedHashSet<>(); + + try (Stream pathStream = Files.walk(sourcePath)) { + pathStream.forEach(new Consumer() { + @Override + public void accept(Path p) { + if (p.toFile().isDirectory()) + return; + List lines; + try { + lines = Files.readAllLines(p); + for (String line : lines) { + if (!line.contains("clip archiválása sikertelen. A rendszer üzenete: null")) + continue; + String[] splittedLine = line.split("'"); + if (names.contains(splittedLine[1])) + continue; + names.add(splittedLine[1]); + + //lenyeli a duplikalt hozzadast, lekezelni. addig nem futtathato!!!!!!! + Map> existing = getExistingItemHouseIDs(splittedLine[1]); + + Media media = (Media) existing.keySet().toArray()[0]; + List itemTitles = (List) existing.values().toArray()[0]; + List rundowns = ListUtils.cast(octopusAPI.getRundownsByPlaceHolderID(splittedLine[1])); + System.out.println(String.format("%s %d: ", splittedLine[1], media.getId())); + + for (BasicDBObject rd : rundowns) { + String rundownTitle = getRundownTitle(rd); + if (itemTitles.contains(rundownTitle) || rundownTitle.contains("00:00")) + continue; + + Item item = getRundownItem(rd); + if (item == null) { + item = manager.createItem("Generic", rundownTitle, null, NoSQLUtils.asString(rd, IOctopusAPI.ID)); + item.add(); + System.out.print(String.format("* %s,", rundownTitle)); + } else { + System.out.print(String.format("%s (%d),", rundownTitle, item.getId())); + } + + List mediaFiles = media.getMediaFiles(); + if (mediaFiles != null) { + for (MediaFile mf : mediaFiles) { + mf.setId(0); + mf.setMediaId(0); + } + } + media.setId(0); + media.setItemId(item.getId()); + media.setPersister(manager); + media.add(); + + } + + System.out.println(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + } + + System.out.println(names.size()); + } + + @Test + public void test1() { + File sourceMediaFile = new File("c:/_downloads/mediacube-backlog-2018-05.pdf"); + String sourceFileName = sourceMediaFile.getName(); + String sourceFile = Paths.get("l:", sourceFileName).toString(); + System.out.println(sourceFile); + } + }