release: 1.6.1

fix #37
fix #38
fix #40
resolve #43
This commit is contained in:
acgnhik 2022-04-16 12:37:51 +08:00
parent adf6e36b8f
commit b4123e7982
19 changed files with 121 additions and 50 deletions

View File

@ -40,11 +40,6 @@ jobs:
- name: Unzip Python archive
run: Expand-Archive -LiteralPath "python.zip" -DestinationPath "build\python"
- name: Enter build directory
run: |
Set-Location -Path "build"
ls
- name: Rename ffmpeg directory
working-directory: build
run: Rename-Item -Path $($env:FFMPEG_ARCHIVE_NAME).Substring(0, $($env:FFMPEG_ARCHIVE_NAME).Length - 4) "ffmpeg"
@ -83,12 +78,6 @@ jobs:
working-directory: build
run: Copy-Item "${{ github.workspace }}\run.bat" -Destination ".\run.bat"
- name: Exit build directory
working-directory: build
run: |
ls
Set-Location -Path ".."
- name: Zip files
run: |
ls build

View File

@ -1,5 +1,10 @@
# 更新日志
## 1.6.1
- 修复 bug (issue #37, 38, 40)
- 接收到错误的数据自动换线路 (issue #43)
## 1.6.0
- 更新 Pushplus 消息推送 url (issue #26)

View File

@ -1,4 +1,4 @@
__prog__ = 'blrec'
__version__ = '1.6.0'
__version__ = '1.6.1'
__github__ = 'https://github.com/acgnhiki/blrec'

View File

@ -84,6 +84,7 @@ class FLVStreamRecorder(
analyse_data=True,
dedup_join=True,
save_extra_metadata=True,
backup_timestamp=True,
)
def update_size(size: int) -> None:
@ -182,6 +183,7 @@ class FLVStreamRecorder(
url = self._get_live_stream_url()
except FlvStreamCorruptedError as e:
logger.warning(repr(e))
self._use_candidate_stream = not self._use_candidate_stream
url = self._get_live_stream_url()
def _streaming(self, url: str) -> None:

View File

@ -387,6 +387,7 @@ class HLSStreamRecorder(
analyse_data=True,
dedup_join=True,
save_extra_metadata=True,
backup_timestamp=True,
)
self._stream_processor.size_updates.subscribe(update_size)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,6 +10,6 @@
<body>
<app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript>
<script src="runtime.23c91f03d62c595a.js" type="module"></script><script src="polyfills.4b08448aee19bb22.js" type="module"></script><script src="main.8a8c73fae6ff9291.js" type="module"></script>
<script src="runtime.459a2fcc68ef4fa7.js" type="module"></script><script src="polyfills.4b08448aee19bb22.js" type="module"></script><script src="main.8a8c73fae6ff9291.js" type="module"></script>
</body></html>

View File

@ -1,6 +1,6 @@
{
"configVersion": 1,
"timestamp": 1649386979751,
"timestamp": 1650082907617,
"index": "/index.html",
"assetGroups": [
{
@ -14,15 +14,15 @@
"/103.5b5d2a6e5a8a7479.js",
"/146.92e3b29c4c754544.js",
"/45.c90c3cea2bf1a66e.js",
"/66.d8b06f1fef317761.js",
"/694.92a3e0c2fc842a42.js",
"/869.95d68b28a4188d76.js",
"/66.8d16a032cbce41ed.js",
"/694.dbcbd5e1953ea55d.js",
"/869.ac675e78fa0ea7cf.js",
"/common.858f777e9296e6f2.js",
"/index.html",
"/main.8a8c73fae6ff9291.js",
"/manifest.webmanifest",
"/polyfills.4b08448aee19bb22.js",
"/runtime.23c91f03d62c595a.js",
"/runtime.459a2fcc68ef4fa7.js",
"/styles.1f581691b230dc4d.css"
],
"patterns": []
@ -1637,9 +1637,9 @@
"/103.5b5d2a6e5a8a7479.js": "cc0240f217015b6d4ddcc14f31fcc42e1c1c282a",
"/146.92e3b29c4c754544.js": "3824de681dd1f982ea69a065cdf54d7a1e781f4d",
"/45.c90c3cea2bf1a66e.js": "e5bfb8cf3803593e6b8ea14c90b3d3cb6a066764",
"/66.d8b06f1fef317761.js": "43676d9dc886b5624dadecc50f17d4972b183d2d",
"/694.92a3e0c2fc842a42.js": "f8f093029b9996b3db0c4e738bf9f8573fba8392",
"/869.95d68b28a4188d76.js": "cd1add38c89b1df3c0783b74c931b51839f1c530",
"/66.8d16a032cbce41ed.js": "a473089370c2fe27f96a778acf1e709dc5770b31",
"/694.dbcbd5e1953ea55d.js": "41973de76799b085188903f46cc6974f20395741",
"/869.ac675e78fa0ea7cf.js": "f45052016cb5201d5784b3f261e719d96bd1b153",
"/assets/animal/panda.js": "fec2868bb3053dd2da45f96bbcb86d5116ed72b1",
"/assets/animal/panda.svg": "bebd302cdc601e0ead3a6d2710acf8753f3d83b1",
"/assets/fill/.gitkeep": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
@ -3234,11 +3234,11 @@
"/assets/twotone/warning.js": "fb2d7ea232f3a99bf8f080dbc94c65699232ac01",
"/assets/twotone/warning.svg": "8c7a2d3e765a2e7dd58ac674870c6655cecb0068",
"/common.858f777e9296e6f2.js": "b68ca68e1e214a2537d96935c23410126cc564dd",
"/index.html": "114f00ffcd1f7fa5aaaa7f2fcf3109f26c77c715",
"/index.html": "be9d178866ccd58dfdc2a4c1b375ab030302163b",
"/main.8a8c73fae6ff9291.js": "41a5a5a8fb5cda4cfa0e28532812594816257122",
"/manifest.webmanifest": "62c1cb8c5ad2af551a956b97013ab55ce77dd586",
"/polyfills.4b08448aee19bb22.js": "8e73f2d42cc13ca353cea5c886d930bd6da08d0d",
"/runtime.23c91f03d62c595a.js": "0819f1120ed1e37c2ad069ef949147450c951069",
"/runtime.459a2fcc68ef4fa7.js": "a68b948e588e75f8cb2fb5315ac41623e7c6ed1e",
"/styles.1f581691b230dc4d.css": "6f5befbbad57c2b2e80aae855139744b8010d150"
},
"navigationUrls": [

View File

@ -1 +1 @@
(()=>{"use strict";var e,v={},m={};function r(e){var i=m[e];if(void 0!==i)return i.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(i,t,f,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,f,o]=e[n],c=!0,l=0;l<t.length;l++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,o<a&&(a=o));if(c){e.splice(n--,1);var d=f();void 0!==d&&(i=d)}}return i}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,f,o]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>(592===e?"common":e)+"."+{45:"c90c3cea2bf1a66e",66:"d8b06f1fef317761",103:"5b5d2a6e5a8a7479",146:"92e3b29c4c754544",592:"858f777e9296e6f2",694:"92a3e0c2fc842a42",869:"95d68b28a4188d76"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="blrec:";r.l=(t,f,o,n)=>{if(e[t])e[t].push(f);else{var a,c;if(void 0!==o)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==i+o){a=u;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",i+o),a.src=r.tu(t)),e[t]=[f];var s=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var _=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),_&&_.forEach(h=>h(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var n=r.o(e,f)?e[f]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=f){var a=new Promise((u,s)=>n=e[f]=[u,s]);o.push(n[2]=a);var c=r.p+r.u(f),l=new Error;r.l(c,u=>{if(r.o(e,f)&&(0!==(n=e[f])&&(e[f]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+f+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var i=(f,o)=>{var l,d,[n,a,c]=o,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(f&&f(o);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[n[u]]=0;return r.O(s)},t=self.webpackChunkblrec=self.webpackChunkblrec||[];t.forEach(i.bind(null,0)),t.push=i.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,v={},m={};function r(e){var i=m[e];if(void 0!==i)return i.exports;var t=m[e]={exports:{}};return v[e].call(t.exports,t,t.exports,r),t.exports}r.m=v,e=[],r.O=(i,t,o,f)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,o,f]=e[n],c=!0,l=0;l<t.length;l++)(!1&f||a>=f)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,f<a&&(a=f));if(c){e.splice(n--,1);var d=o();void 0!==d&&(i=d)}}return i}f=f||0;for(var n=e.length;n>0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,o,f]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>(592===e?"common":e)+"."+{45:"c90c3cea2bf1a66e",66:"8d16a032cbce41ed",103:"5b5d2a6e5a8a7479",146:"92e3b29c4c754544",592:"858f777e9296e6f2",694:"dbcbd5e1953ea55d",869:"ac675e78fa0ea7cf"}[e]+".js",r.miniCssF=e=>{},r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="blrec:";r.l=(t,o,f,n)=>{if(e[t])e[t].push(o);else{var a,c;if(void 0!==f)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==i+f){a=u;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",i+f),a.src=r.tu(t)),e[t]=[o];var s=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var _=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),_&&_.forEach(h=>h(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;r.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(o,f)=>{var n=r.o(e,o)?e[o]:void 0;if(0!==n)if(n)f.push(n[2]);else if(666!=o){var a=new Promise((u,s)=>n=e[o]=[u,s]);f.push(n[2]=a);var c=r.p+r.u(o),l=new Error;r.l(c,u=>{if(r.o(e,o)&&(0!==(n=e[o])&&(e[o]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+o+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+o,o)}else e[o]=0},r.O.j=o=>0===e[o];var i=(o,f)=>{var l,d,[n,a,c]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(o&&o(f);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[n[u]]=0;return r.O(s)},t=self.webpackChunkblrec=self.webpackChunkblrec||[];t.forEach(i.bind(null,0)),t.push=i.bind(null,t.push.bind(t))})()})();

View File

@ -37,8 +37,15 @@ __all__ = 'FlvParser', 'FlvDumper'
class FlvParser:
def __init__(self, stream: RandomIO) -> None:
def __init__(
self,
stream: RandomIO,
backup_timestamp: bool = False,
restore_timestamp: bool = False,
) -> None:
self._stream = stream
self._backup_timestamp = backup_timestamp
self._restore_timestamp = restore_timestamp
self._reader = StructReader(stream)
def parse_header(self) -> FlvHeader:
@ -108,19 +115,42 @@ class FlvParser:
def parse_flv_tag_header(self, data: bytes) -> FlvTagHeader:
reader = StructReader(BytesIO(data))
flag = reader.read_ui8()
filtered = bool(flag & 0b0010_0000)
if filtered:
raise FlvDataError('Unsupported Filtered FLV Tag', data)
tag_type = TagType(flag & 0b0001_1111)
data_size = reader.read_ui24()
timestamp = reader.read_ui24()
timestamp_extended = reader.read_ui8()
timestamp = timestamp_extended << 24 | timestamp
stream_id = reader.read_ui24()
return FlvTagHeader(
filtered, tag_type, data_size, timestamp, stream_id
)
if self._backup_timestamp:
return FlvTagHeader(
filtered=filtered,
tag_type=tag_type,
data_size=data_size,
timestamp=timestamp_extended << 24 | timestamp,
stream_id=timestamp,
)
elif self._restore_timestamp:
return FlvTagHeader(
filtered=filtered,
tag_type=tag_type,
data_size=data_size,
timestamp=stream_id,
stream_id=stream_id,
)
else:
return FlvTagHeader(
filtered=filtered,
tag_type=tag_type,
data_size=data_size,
timestamp=timestamp_extended << 24 | timestamp,
stream_id=stream_id,
)
def parse_audio_tag_header(self, data: bytes) -> AudioTagHeader:
reader = StructReader(BytesIO(data))

View File

@ -16,9 +16,21 @@ logger = logging.getLogger(__name__)
class FlvReader:
def __init__(self, stream: RandomIO) -> None:
def __init__(
self,
stream: RandomIO,
*,
backup_timestamp: bool = False,
restore_timestamp: bool = False,
) -> None:
self._stream = stream
self._parser = FlvParser(stream)
self._backup_timestamp = backup_timestamp
self._restore_timestamp = restore_timestamp
self._parser = FlvParser(
stream,
backup_timestamp=backup_timestamp,
restore_timestamp=restore_timestamp,
)
def read_header(self) -> FlvHeader:
header = self._parser.parse_header()

View File

@ -31,7 +31,8 @@ from .exceptions import (
CutStream,
)
from .common import (
is_audio_tag, is_video_tag, is_metadata_tag, parse_metadata,
is_metadata_tag, parse_metadata, is_audio_tag, is_video_tag,
is_video_sequence_header, is_audio_sequence_header,
is_audio_data_tag, is_video_data_tag, enrich_metadata, update_metadata,
is_data_tag, read_tags_in_duration,
)
@ -63,6 +64,8 @@ class StreamProcessor:
analyse_data: bool = False,
dedup_join: bool = False,
save_extra_metadata: bool = False,
backup_timestamp: bool = False,
restore_timestamp: bool = False,
) -> None:
self._file_manager = file_manager
self._parameters_checker = ParametersChecker()
@ -79,6 +82,8 @@ class StreamProcessor:
self._analyse_data = analyse_data
self._dedup_join = dedup_join
self._save_x_metadata = save_extra_metadata
self._backup_timestamp = backup_timestamp
self._restore_timestamp = restore_timestamp
self._cancelled: bool = False
self._finalized: bool = False
@ -223,7 +228,12 @@ class StreamProcessor:
def _process_stream(self, stream: RandomIO) -> None:
logger.debug(f'Processing the {self._stream_count}th stream...')
self._in_reader = FlvReaderWithTimestampFix(stream)
self._in_reader = FlvReaderWithTimestampFix(
stream,
backup_timestamp=self._backup_timestamp,
restore_timestamp=self._restore_timestamp,
)
flv_header = self._read_header()
self._has_audio = flv_header.has_audio()
@ -552,18 +562,23 @@ class StreamProcessor:
return header
def _ensure_ts_correct(self, tag: FlvTag) -> None:
if not is_audio_data_tag(tag) or not is_video_data_tag(tag):
if not is_audio_data_tag(tag) and not is_video_data_tag(tag):
return
if tag.timestamp + self._delta < 0:
self._delta = -tag.timestamp
logger.warning('Incorrect timestamp: {}, new delta: {}'.format(
tag, self._delta
))
self._delta = (
self._last_tags[0].timestamp +
self._in_reader.calc_interval(tag) - tag.timestamp
)
logger.warning(
f'Incorrect timestamp, updated delta: {self._delta}\n'
f'last output tag: {self._last_tags[0]}\n'
f'current tag: {tag}'
)
def _correct_ts(self, tag: FlvTag, delta: int) -> FlvTag:
if delta == 0 and tag.timestamp >= 0:
return tag
return tag.evolve(timestamp=max(0, tag.timestamp + delta))
return tag.evolve(timestamp=tag.timestamp + delta)
def _calc_delta_duplicated(self, last_duplicated_tag: FlvTag) -> int:
return self._last_tags[0].timestamp - last_duplicated_tag.timestamp
@ -764,8 +779,17 @@ class RobustFlvReader(FlvReader):
class FlvReaderWithTimestampFix(RobustFlvReader):
def __init__(self, stream: RandomIO) -> None:
super().__init__(stream)
def __init__(
self,
stream: RandomIO,
backup_timestamp: bool = False,
restore_timestamp: bool = False,
) -> None:
super().__init__(
stream,
backup_timestamp=backup_timestamp,
restore_timestamp=restore_timestamp,
)
self._last_tag: Optional[FlvTag] = None
self._last_video_tag: Optional[VideoTag] = None
self._last_audio_tag: Optional[AudioTag] = None
@ -842,11 +866,17 @@ class FlvReaderWithTimestampFix(RobustFlvReader):
if is_video_tag(tag):
if self._last_video_tag is None:
return False
return tag.timestamp <= self._last_video_tag.timestamp
if is_video_sequence_header(self._last_video_tag):
return tag.timestamp < self._last_video_tag.timestamp
else:
return tag.timestamp <= self._last_video_tag.timestamp
elif is_audio_tag(tag):
if self._last_audio_tag is None:
return False
return tag.timestamp <= self._last_audio_tag.timestamp
if is_audio_sequence_header(self._last_audio_tag):
return tag.timestamp < self._last_audio_tag.timestamp
else:
return tag.timestamp <= self._last_audio_tag.timestamp
else:
return False

View File

@ -1,8 +1,10 @@
<nz-modal
nzTitle="修改文件路径模板"
nzCentered
[nzFooter]="modalFooter"
[(nzVisible)]="visible"
[nzOkDisabled]="control.invalid || control.value.trim() === value"
(nzOnOk)="handleConfirm()"
(nzOnCancel)="handleCancel()"
>
<ng-container *nzModalContent>
<form nz-form [formGroup]="settingsForm">

View File

@ -28,7 +28,7 @@ export function toBitRateString(
let unit: string;
if (bitrate <= 0) {
return '0 kbps/s';
return '0' + spacer + 'kbps';
}
if (bitrate < 1e6) {
@ -60,7 +60,7 @@ export function toByteRateString(
let unit: string;
if (rate <= 0) {
return '0B/s';
return '0' + spacer + 'B/s';
}
if (rate < 1e3) {

View File

@ -86,7 +86,7 @@
<span class="label">下载速度</span>
<app-wave-graph [value]="data.task_status.dl_rate"></app-wave-graph>
<span class="value">
{{ data.task_status.dl_rate | datarate: { bitrate: true } }}
{{ data.task_status.dl_rate * 8 | datarate: { bitrate: true } }}
</span>
</li>
<li class="info-item">