private void Media_AfterUpdateBootstrap(object sender, EventArgs e) { Media media = sender as Media; if (media == null) { return; } if (fromTimestamp > 0 && media.Bootstrap != null && !media.Bootstrap.live) { media.SetCurrentFragmentIndexByTimestamp(fromTimestamp); } if (Program.debug) { Program.DebugLog("Segment Tables:"); for (int i = 0; i < media.Bootstrap.segmentRunTables.Count; i++) { Program.DebugLog(String.Format(" Table {0} (Count={1})", i + 1, media.Bootstrap.segmentRunTables[i].segmentFragmentPairs.Count)); Program.DebugLog(" FirstSegment FragPerSegment FragmentsAccrued"); foreach (var p in media.Bootstrap.segmentRunTables[i].segmentFragmentPairs) { Program.DebugLog(String.Format(" {0,-12} {1,-14} {2,-16}", p.firstSegment, p.fragmentsPerSegment, p.fragmentsAccrued)); } } Program.DebugLog("Fragment Tables:"); for (int i = 0; i < media.Bootstrap.fragmentRunTables.Count; i++) { Program.DebugLog(String.Format(" Table {0} (Count={1})", i + 1, media.Bootstrap.fragmentRunTables[i].fragmentDurationPairs.Count)); Program.DebugLog(" FirstFragment Duration DurationAccrued DiscontinuityIndicator"); foreach (var p in media.Bootstrap.fragmentRunTables[i].fragmentDurationPairs) { Program.DebugLog(String.Format(" {0,-13} {1,-8} {2,-15} {3,-22}", p.firstFragment, p.duration, p.durationAccrued, p.discontinuityIndicator)); } } Program.DebugLog("Start fragment : " + media.CurrentFragmentIndex); Program.DebugLog("Total fragments: " + media.TotalFragments + "\n"); } // Add all fragments to download queue for (uint fragIndex = media.CurrentFragmentIndex; fragIndex <= media.TotalFragments; fragIndex++) { FragmentDurationPair fragmentInfo = media.Bootstrap.GetFragmentInfo(fragIndex); if (fragmentInfo == null) { throw new InvalidOperationException("No info for fragment " + fragIndex); } if (fragmentInfo.discontinuityIndicator != 0) { Program.DebugLog("Skipping fragment " + fragIndex + " due to discontinuity, Type: " + fragmentInfo.discontinuityIndicator); continue; } Downloader.AddMediaFragmentToDownload(media, fragIndex); } }
public void KDF() { // Decrypt packet salt if (ecmID != prevEcmID) { byte[] saltHmacKey = sha1.ComputeHash(F4FOldMethod.AppendBuf(sessionKey, packetIV)); if (debug) { Program.DebugLog("SaltHmacKey : " + Hexlify(saltHmacKey)); } shaSalt.Key = saltHmacKey; saltAesKey = F4FOldMethod.BlockCopy(shaSalt.ComputeHash(hmacData1), 0, 16); if (debug) { Program.DebugLog("SaltAesKey : " + Hexlify(saltAesKey)); } prevEcmID = ecmID; } if (debug) { Program.DebugLog("EncryptedSalt: " + Hexlify(packetSalt)); } byte[] decryptedSalt = AesDecrypt(packetSalt, saltAesKey, packetIV); if (decryptedSalt == null) { Program.Quit("<c:Red>Error ocurred while decription salt of fagment."); } if (debug) { Program.DebugLog("DecryptedSalt: " + Hexlify(decryptedSalt)); } decryptBytes = F4FOldMethod.ReadInt32(ref decryptedSalt, 0); if (debug) { Program.DebugLog("DecryptBytes : " + decryptBytes); } byte[] decryptedSalt2 = F4FOldMethod.BlockCopy(decryptedSalt, 4, 16); if (debug) { Program.DebugLog("DecryptedSalt: " + Hexlify(decryptedSalt2)); } // Generate final packet decryption key byte[] finalHmacKey = sha1.ComputeHash(decryptedSalt2); if (debug) { Program.DebugLog("FinalHmacKey : " + Hexlify(finalHmacKey)); } finalsha.Key = finalHmacKey; packetKey = F4FOldMethod.BlockCopy(finalsha.ComputeHash(hmacData2), 0, 16); if (debug) { Program.DebugLog("PacketKey : " + Hexlify(packetKey)); } }
public static HttpWebResponse GetResponseNoException(HttpWebRequest req) { try { return((HttpWebResponse)req.GetResponse()); } catch (WebException we) { Program.DebugLog("Error downloading the link: " + req.RequestUri + "\r\nException: " + we.Message); var resp = we.Response as HttpWebResponse; if (resp == null) { Program.Quit("<c:Red>" + we.Message + " (Request status: <c:Magenta>" + we.Status + "</c>)"); } //throw; return(resp); } }
public void DownloadFragment(TagsStore tagsStore) { string fragmentUrl = media.GetFragmentUrl(fragIndex); Program.DebugLog("Fragment Url: " + fragmentUrl); byte[] data = HTTP.TryGETData(fragmentUrl, out int retCode, out string status); int retries = 0; while (retCode >= 500 && retries <= MaxRetriesLoad) { System.Threading.Thread.Sleep(1000); retries++; data = HTTP.TryGETData(fragmentUrl, out retCode, out status); } if (retCode != 200) { string msg = "Download fragment failed " + fragIndex + "/" + media.TotalFragments + " code: " + retCode + " status: " + status; Program.DebugLog(msg); if (Program.verbose) { Program.Message(msg); } if (media.Bootstrap.live) { //media.CurrentFragmentIndex = media.TotalFragments; tagsStore.Complete = true; Done(media); return; } else { throw new InvalidOperationException(status); } } Program.DebugLog("Downloaded: fragment=" + fragIndex + "/" + media.TotalFragments + " lenght: " + data.Length); var boxes = Box.GetBoxes(data); if (boxes.Find(i => i.Type == F4FConstants.BOX_TYPE_MDAT) is MediaDataBox mdat) { lock (tagsStore) { FLVTag.GetVideoAndAudioTags(tagsStore, mdat.data); tagsStore.ARFA = boxes.Find(i => i.Type == F4FConstants.BOX_TYPE_AFRA) as AdobeFragmentRandomAccessBox; tagsStore.Complete = true; } HDSDownloader.LiveIsStalled = false; } else if (media.Bootstrap.live) { HDSDownloader.LiveIsStalled = true; } else { throw new InvalidOperationException("No found mdat box in fragment " + fragIndex + "/" + media.TotalFragments); } if (Program.verbose) { Program.Message(string.Format("Media: {0} Downloaded: {1} Data size: {2}", media.label, fragIndex, data.Length)); } media.CurrentFragmentIndex++; media.Downloaded++; Done(media); }
public byte[] Decrypt(byte[] data, long pos, string baseUrl, string auth) { if (debug) { Program.DebugLog("\n----- Akamai Decryption Start -----"); } byte[] decryptedData = new byte[0]; // Parse packet header using (MemoryStream ms = new MemoryStream(data)) { ms.Position = pos; using (HDSBinaryReader br = new HDSBinaryReader(ms)) { int b = br.ReadByte(); ecmVersion = (b >> 4); if (ecmVersion != 11) { ecmVersion = b; } ecmID = br.ReadInt32(); ecmTimestamp = (uint)br.ReadInt32(); kdfVersion = br.ReadInt16(); dccAccReserved = br.ReadByte(); if (debug) { Program.DebugLog("ECM Version : " + ecmVersion + ", ECM ID: " + ecmID + ", ECM Timestamp: " + ecmTimestamp + ", KDF Version: " + kdfVersion + ", DccAccReserved: " + dccAccReserved); } b = br.ReadByte(); bool iv = ((b & 2) > 0); bool key = ((b & 4) > 0); if (iv) { packetIV = br.ReadBytes(16); if (debug) { Program.DebugLog("PacketIV : " + Hexlify(packetIV)); } } if (key) { sessionKeyUrl = br.ReadString(); if (debug) { Program.DebugLog("SessionKeyUrl: " + sessionKeyUrl); } string keyPath = sessionKeyUrl.Substring(sessionKeyUrl.LastIndexOf('/')); string keyUrl = HTTP.JoinUrl(baseUrl, keyPath) + auth; // Download key file if required if (sessionKeyUrl != lastKeyUrl) { if ((baseUrl.Length == 0) && (sessionKey.Length == 0)) { if (debug) { Program.DebugLog("Unable to download session key without manifest url. you must specify it manually using 'adkey' switch."); } } else { if (baseUrl.Length > 0) { if (debug) { Program.DebugLog("Downloading new session key from " + keyUrl); } byte[] downloadedData = HTTP.TryGETData(keyUrl, out int retCode, out string status); if (retCode == 200) { sessionID = "_" + keyPath.Substring("/key_".Length); sessionKey = downloadedData; } else { if (debug) { Program.DebugLog("Failed to download new session key, Status: " + status + " (" + retCode + ")"); } sessionID = ""; } } } lastKeyUrl = sessionKeyUrl; if (sessionKey == null || sessionKey.Length == 0) { Program.Quit("Failed to download akamai session decryption key"); } } } if (debug) { Program.DebugLog("SessionKey : " + Hexlify(sessionKey)); } if (sessionKey == null || sessionKey.Length < 1) { Program.Quit("ERROR: Fragments can't be decrypted properly without corresponding session key."); } byte reserved; byte[] reservedBlock1, reservedBlock2, encryptedData, lastBlockData; reserved = br.ReadByte(); packetSalt = br.ReadBytes(32); reservedBlock1 = br.ReadBytes(20); reservedBlock2 = br.ReadBytes(20); if (debug) { Program.DebugLog("ReservedByte : " + reserved + ", ReservedBlock1: " + Hexlify(reservedBlock1) + ", ReservedBlock2: " + Hexlify(reservedBlock2)); } // Generate packet decryption key KDF(); // Decrypt packet data encryptedData = br.ReadBytes((int)decryptBytes); lastBlockData = br.ReadToEnd(); if (decryptBytes > 0) { decryptedData = AesDecrypt(encryptedData, packetKey, packetIV); } decryptedData = F4FOldMethod.AppendBuf(decryptedData, lastBlockData); if (debug) { Program.DebugLog("EncryptedData: " + Hexlify(encryptedData, 64)); Program.DebugLog("DecryptedData: " + Hexlify(decryptedData, 64)); Program.DebugLog("----- Akamai Decryption End -----\n"); } } // using (HDSBinaryReader br = new HDSBinaryReader(ms)) } // using (MemoryStream ms = new MemoryStream(data)) return(decryptedData); }
public static void FixTimestamp(DecoderLastState DecoderState, FLVTag tag) { uint lastTS = DecoderState.prevVideoTS >= DecoderState.prevAudioTS ? DecoderState.prevVideoTS : DecoderState.prevAudioTS; uint fixedTS = lastTS + (uint)fixWindow; if ((DecoderState.baseTS == DecoderLastState.INVALID_TIMESTAMP) && ((tag.Type == FLVTag.TagType.AUDIO) || (tag.Type == FLVTag.TagType.VIDEO))) { DecoderState.baseTS = tag.Timestamp; } if ((DecoderState.baseTS > fixWindow) && (tag.Timestamp >= DecoderState.baseTS)) { tag.Timestamp -= DecoderState.baseTS; } if (lastTS != DecoderLastState.INVALID_TIMESTAMP) { int timeShift = (int)(tag.Timestamp - lastTS); if (timeShift > fixWindow) { Program.DebugLog(string.Format("Timestamp gap detected: PacketTS={0} LastTS={1} Timeshift={2}", tag.Timestamp, lastTS, timeShift)); if (DecoderState.baseTS < tag.Timestamp) { DecoderState.baseTS += (uint)(timeShift - fixWindow); } else { DecoderState.baseTS = (uint)(timeShift - fixWindow); } tag.Timestamp = fixedTS; } else { lastTS = tag.Type == FLVTag.TagType.VIDEO ? DecoderState.prevVideoTS : DecoderState.prevAudioTS; if ((lastTS != DecoderLastState.INVALID_TIMESTAMP) && (int)tag.Timestamp < (lastTS - fixWindow)) { if ((DecoderState.negTS != DecoderLastState.INVALID_TIMESTAMP) && ((tag.Timestamp + DecoderState.negTS) < (lastTS - fixWindow))) { DecoderState.negTS = DecoderLastState.INVALID_TIMESTAMP; } if (DecoderState.negTS == DecoderLastState.INVALID_TIMESTAMP) { DecoderState.negTS = fixedTS - tag.Timestamp; Program.DebugLog(string.Format("Negative timestamp detected: PacketTS={0} LastTS={1} NegativeTS={2}", tag.Timestamp, lastTS, DecoderState.negTS)); tag.Timestamp = (uint)fixedTS; } else { if ((tag.Timestamp + DecoderState.negTS) <= (lastTS + fixWindow)) { tag.Timestamp += (uint)DecoderState.negTS; } else { DecoderState.negTS = fixedTS - tag.Timestamp; Program.DebugLog(string.Format("Negative timestamp override: PacketTS={0} LastTS={1} NegativeTS={2}", tag.Timestamp, lastTS, DecoderState.negTS)); tag.Timestamp = (uint)fixedTS; } } } } } if (tag is FLVTagAudio) { DecoderState.prevAudioTS = tag.Timestamp; } else { DecoderState.prevVideoTS = tag.Timestamp; } }
private Media QualitySelectionForMedia(List <Media> mediaList, ref string avaliable, bool collectBitrates, bool isAlt = false) { if (collectBitrates) { avaliable = " "; } Media selected = null; string[] langs = lang.ToLower().Split(','); int indexLang = 99; // sorting media by quality mediaList.Sort((a, b) => b.bitrate.CompareTo(a.bitrate)); if (mediaList.Count > 0) { foreach (Media media in mediaList) { if (isAlt) { if (collectBitrates) { avaliable += media.label + " "; } Program.DebugLog(String.Format(" {0,-8}{1}", media.label, media.url)); //selected by language, codec, bitrate or label if (!string.IsNullOrEmpty(alt)) { string altLow = alt.ToLower(); if (altLow == media.label.ToLower() || altLow == media.lang.ToLower() || altLow == media.bitrate.ToString() || altLow == media.streamId.ToLower()) { selected = media; } else if (media.audioCodec.ToLower().IndexOf(altLow) >= 0) { selected = media; } else if (media.videoCodec.ToLower().IndexOf(altLow) >= 0) { selected = media; } } if (!string.IsNullOrEmpty(lang)) { int idx = Array.IndexOf(langs, media.lang.ToLower()); if (idx >= 0 && idx < indexLang) { indexLang = idx; selected = media; } } } else { if (collectBitrates) { avaliable += media.bitrate + " "; } Program.DebugLog(String.Format(" {0,-8}{1}", media.bitrate, media.url)); if (media.bitrate.ToString() == quality) { selected = media; } } } if (selected == null && !isAlt) { if (int.TryParse(quality, out int iQuality)) { // search nearest bitrate int minDiff = int.MaxValue; foreach (var m in mediaList) { int diff = Math.Abs(m.bitrate - iQuality); if (diff < minDiff) { minDiff = diff; selected = m; } } } else { switch (quality.ToLower()) { case "low": selected = mediaList[mediaList.Count - 1]; break; case "medium": selected = mediaList[mediaList.Count / 2]; break; default: selected = mediaList[0]; break; // first } } } } return(selected); }
private void GetManifestAndSelectMedia(string manifestUrl, int nestedBitrate = 0, int level = 0) { if (level > MAX_LEVEL_NESTED_MANIFESTS) { throw new InvalidOperationException("Maximum nesting level reached of multi-level manifests."); } XmlNodeEx xmlManifest = LoadXml(manifestUrl); if (string.IsNullOrEmpty(baseUrl)) { baseUrl = URL.ExtractBaseUrl(manifestUrl); } // parse the manifest manifest = new Manifest(xmlManifest, baseUrl, "", nestedBitrate); if (string.IsNullOrEmpty(manifest.baseURL)) { throw new InvalidOperationException("Not found <c:Magenta>baseURL</c> value in manifest or in parameter <c:White>--urlbase</c>."); } if (manifest.media.Count < 1) { throw new InvalidOperationException("No media entry found in the manifest"); } Program.DebugLog("Manifest entries:\n"); Program.DebugLog(String.Format(" {0,-8}{1}", "Bitrate", "URL")); // TEST for alternate selection if (testalt) { manifest.alternativeMedia.AddRange(manifest.media); } // Quality selection selectedMedia = QualitySelectionForMedia(manifest.media, ref avaliableBitrates, level < 1); // Quality selection for alternative media selectedMediaAlt = QualitySelectionForMedia(manifest.alternativeMedia, ref avaliableAlt, level < 1, true); if (selectedMedia == null) { selectedMedia = manifest.media[0]; } // check for multi-level manifest if (!string.IsNullOrEmpty(selectedMedia.href)) { string nestedManifestUrl = URL.getAbsoluteUrl(manifest.baseURL, selectedMedia.href); baseUrl = URL.ExtractBaseUrl(nestedManifestUrl); nestedBitrate = selectedMedia.bitrate; selectedMedia = null; GetManifestAndSelectMedia(nestedManifestUrl, nestedBitrate, level + 1); return; } string sQuality = selectedMedia.bitrate.ToString(); int n = Math.Max(0, avaliableBitrates.IndexOf(sQuality)); avaliableBitrates = avaliableBitrates.Replace(" " + sQuality + " ", " <c:Cyan>" + sQuality + "</c> "); Program.Message("Quality Selection:"); Program.Message("Available:" + avaliableBitrates); Program.Message("Selected : <c:Cyan>" + sQuality.PadLeft(n + sQuality.Length - 1)); if (manifest.alternativeMedia.Count > 0) { Program.Message("Alternatives:" + avaliableAlt); if (selectedMediaAlt != null) { string label = selectedMediaAlt.label; n = avaliableAlt.IndexOf(label); avaliableAlt = avaliableAlt.Replace(" " + label + " ", " <c:Cyan>" + label + "</c> "); Program.Message("Selected : <c:Cyan>" + label.PadLeft(n + label.Length - 1)); // get bootstrap for media from manifest by id if (!string.IsNullOrEmpty(selectedMediaAlt.bootstrapInfo?.id) && (selectedMediaAlt.bootstrapInfo.data == null)) { selectedMediaAlt.bootstrapInfo = manifest.bootstrapInfos.Find(i => i.id == selectedMediaAlt.bootstrapInfo.id); } } } // get bootstrap for media from manifest by id if (!string.IsNullOrEmpty(selectedMedia.bootstrapInfo?.id) && (selectedMedia.bootstrapInfo.data == null)) { selectedMedia.bootstrapInfo = manifest.bootstrapInfos.Find(i => i.id == selectedMedia.bootstrapInfo.id); } if (selectedMedia.bootstrapInfo == null) { throw new InvalidOperationException("No bootstrapInfo for selected media entry"); } if (!Program.fproxy) { HTTP.notUseProxy = true; } // Use embedded auth information when available int idx = selectedMedia.url.IndexOf('?'); if (idx > 0) { auth = selectedMedia.url.Substring(idx); selectedMedia.url = selectedMedia.url.Substring(0, idx); } if (selectedMedia.metadata != null) { FLVFile.onMetaData = new FLVTagScriptBody(selectedMedia.metadata); } selectedMedia.AfterUpdateBootstrap += Media_AfterUpdateBootstrap; selectedMedia.UpdateBootstrapInfo(); if (selectedMedia.Bootstrap.live) { Program.Message("<c:Magenta>[Live stream]"); } if (selectedMediaAlt == selectedMedia) { selectedMediaAlt = null; } if (selectedMediaAlt != null) { selectedMediaAlt.alternate = true; // Use embedded auth information when available idx = selectedMediaAlt.url.IndexOf('?'); if (idx > 0) { auth = selectedMediaAlt.url.Substring(idx); selectedMediaAlt.url = selectedMediaAlt.url.Substring(0, idx); } selectedMediaAlt.AfterUpdateBootstrap += Media_AfterUpdateBootstrap; selectedMediaAlt.UpdateBootstrapInfo(); } }