RTSP 1.0 client and server library for the Go programming language

Overview

gortsplib

Test Lint Go Report Card CodeCov PkgGoDev

RTSP 1.0 client and server library for the Go programming language, written for rtsp-simple-server.

Go ≥ 1.14 is required.

Features:

  • Client
    • Query servers about available streams
    • Read
      • Read streams from servers with UDP, UDP-multicast, TCP or TLS
      • Switch protocol automatically (switch to TCP in case of server error or UDP timeout)
      • Read only selected tracks of a stream
      • Pause or seek without disconnecting from the server
      • Generate RTCP receiver reports automatically
    • Publish
      • Publish streams to servers with UDP, TCP or TLS
      • Switch protocol automatically (switch to TCP in case of server error)
      • Pause without disconnecting from the server
      • Generate RTCP sender reports automatically
  • Server
    • Handle requests from clients
    • Sessions and connections are independent
    • Read streams from clients with UDP, TCP or TLS
    • Write streams to clients with UDP, UDP-multicast, TCP or TLS
    • Provide SSRC, RTP-Info to clients automatically
    • Generate RTCP receiver reports automatically
  • Utilities
    • Encode and decode RTSP primitives, RTP/H264, RTP/AAC, SDP

Table of contents

Examples

API Documentation

https://pkg.go.dev/github.com/aler9/gortsplib#pkg-index

Links

Related projects

IETF Standards

Conventions

Comments
  • Support AAC tracks with custom sizelength, indexlength and indexdeltalength

    Support AAC tracks with custom sizelength, indexlength and indexdeltalength

    repull for #114

    When proxying a rtsp, there is only sizelength, no indexlength in its sdp, , in aac payload, AU-headers-length is 000d, not 0010, so it will be error invalid AU-headers-length (13) when decoding.

    sdp info:

    v=0
    o=- 040d498c 1 IN IP4 172.20.64.45
    s=server name:ams32 172.20.64.45
    i=stream/0
    t=0 0
    a=tool:AMS32 v1.0
    a=type:broadcast
    a=type:control:*
    a=range:npt=0-
    m=video 0 RTP/AVP 96
    c=IN IP4 0.0.0.0
    a=rtpmap:96 H264/90000
    a=control:track1
    a=fmtp:96 sprop-parameter-sets=Z2QAKq2EAQwgCGEAQwgCGEAQwgCEO1A8ARPywgAAAwACAAADAHkI,aO48sA==
    m=audio 0 RTP/AVP 97
    c=IN IP4 0.0.0.0
    a=rtpmap:97 mpeg4-generic/48000/2
    a=control:track2
    a=fmtp: 97 streamtype=3;profile-level-id=14;mode=AAC-hbr;config=1190;sizeLength=13
    
    opened by BenLocal 8
  • Question : onSegment

    Question : onSegment

    Hi, I just wanted to know how one could run code for every segment (I'm trying to store the segments individually).

    I think it would be possible to build something on top of onFrame, but I'm not sure whether the frame is actually the segment or really a frame of the video feed.

    Thank you for your time

    question 
    opened by Alex-S-H-P 8
  • client: add ability to disable RTP sender checks

    client: add ability to disable RTP sender checks

    In some cases that would be useful to disable RTP sender IP:PORT checks.

    In my case, I was having issues doing playback from an RTSP server which doesn't support RTCP, and then uses server_port=0-0 in SETUP's reply Transport: header.

    opened by lionelnicolas 8
  • Persist credentials on source redirections

    Persist credentials on source redirections

    Background

    Consider a source RTSP server that requires a set of username/password credentials. The source server is also load balancing between its' other nodes. Source server responds with a redirected URL, however, due to the nature of web redirection, the credentials used to access the source server is not included in the redirection URL. When gortsplib is handling the redirection, it does not factor in any credentials.

    Solution

    The proposed changes in this PR adds the flag PersistCredentialsOnRedirect to the Client, setting it to true results in the originally used credentials (username/password) being carried over onto the redirected URL (when the source server responds with the redirection response). This is ideal for source servers that are consistently redirecting to one of its' other servers and uses the same set of credentials for each server.

    Tests

    I've modified the TestClientReadRedirect test case to cater for the proposed changes and built it locally, targeting linux/amd64 and used it to build the rtsp-simple-server. The locally built rtsp-simple-server binary was tested successfully with several live feeds that falls under this need.

    Once this is reviewed and published, I can create the PR for the rtsp-simple-server that includes this flag as a part of the path config for a RTSP source.

    opened by hiruna 7
  • Something wrong when parseing package,and it will be timeout

    Something wrong when parseing package,and it will be timeout

    Hello, when I use gortsplib to pull streams, I connect to Dahua dss-h8900 platform, and some stream connection times out (VLC and Dahua plug-ins can be used normally). There may be a problem with the parsing package. The relevant logs are as follows:the ip is from 10.105.243.19 to 10.105 240.22

    2022-04-27T18.log

    data.zip

    bug 
    opened by liumaopeng 7
  • track: expose MediaDescription() and Set/GetControl()

    track: expose MediaDescription() and Set/GetControl()

    Prior to commit 6d5bf0c1bb90f8e6b3c80acc32ac37f958075303, gortsplib clients could access the media description through the Media field, which could be used to access SDP fields and media type (audio vs. video). When this was replaced with the private mediaDescription, that ability was lost. This PR exposes MediaDescription() to restore access to these fields.

    opened by tmatth 6
  • Suggesting decrease of rtpPayloadMaxSize to 1200

    Suggesting decrease of rtpPayloadMaxSize to 1200

    The rtpPayloadMaxSize (https://github.com/aler9/gortsplib/blob/main/pkg/rtph264/encoder.go#L13) is fixed to 1460, which is too large to fit a broader range of network configurations, resulting in fragmentations on the IP-layer or a complete packet drop in certain settings. Pion e.g. uses 1200 (https://github.com/pion/webrtc/blob/v3.1.24/constants.go#L27) for outbound RTP packets.

    enhancement 
    opened by flo-eyeson-team 5
  • Example for reading an RTSP stream and republishing all tracks to a server

    Example for reading an RTSP stream and republishing all tracks to a server

    This may be a weird usecase, but I'm trying to use gortsplib to read an RTSP stream from a camera, then publish it to an endpoint configured on rtsp-simple-server. I've attempted to do so myself, but my naive attempt isn't working correctly. See kaiiorg/client-read-publish. For the record, I'm new to Go, so I may have committed some cardinal sin without realizing; sorry for that.

    Additional information:

    1. I'm using rtsp-simple-server 0.17.13.
    2. rtsp-simple-server's config is the default provided with the linux binary, but the paths key was modified:
    paths:
      PublishedStream:
        source: publisher
      ExampleStream:
        # Just using rtsp-simple-server for consistency, a realistic usage would be connecting directly to a camera.
        source: rtsp://admin:[email protected]/media/video1
    
    1. ffplay works when I stream the ExampleStream:
      • ffplay.exe -x 500 -rtsp_transport tcp "rtsp://192.168.62.145:8554/ExampleStream"
      • image
    2. ffplay does not work when I attempt to stream my republished stream:
      • ffplay.exe -x 500 -rtsp_transport tcp "rtsp://192.168.62.145:8554/PublishedStream"
      • image
    3. Setting both rtsp-simple-server and my test program to use TCP as the transport makes no change

    Full Usecase

    One of the requirements of the program I'm writing is to:

    1. Get request via HTTP or MQTT
    2. Read the stream from the IP camera
      • This may require adding a header to the RTSP request(s) to stream stored files on the camera instead of the live stream. Its weird, I know.
    3. Publish the stream to an instance of rtsp-simple-server. The publish url would be in the HTTP/MQTT message. This issue would handle parts of 2 and 3.
    question 
    opened by kaiiorg 5
  • client: allow UserAgent to be set

    client: allow UserAgent to be set

    It's helpful for debugging to be able to override the hardcoded gortsplib User-Agent header, as we can have multiple gortsplib-based clients. I'd also be open to making this a config option or a parameter to the Client, but this was the lowest impact change.

    It also has the advantage that this parameter can be changed by e.g., docker image without rebuilding.

    opened by tmatth 4
  • aac decode with sdp sizelength and indexlength

    aac decode with sdp sizelength and indexlength

    When proxying a rtsp, there is only sizelength, no indexlength in its sdp, , in aac payload, AU-headers-length is 000d, not 0010, so it will be error invalid AU-headers-length (13) when decoding.

    sdp info:

    v=0
    o=- 040d498c 1 IN IP4 172.20.64.45
    s=server name:ams32 172.20.64.45
    i=stream/0
    t=0 0
    a=tool:AMS32 v1.0
    a=type:broadcast
    a=type:control:*
    a=range:npt=0-
    m=video 0 RTP/AVP 96
    c=IN IP4 0.0.0.0
    a=rtpmap:96 H264/90000
    a=control:track1
    a=fmtp:96 sprop-parameter-sets=Z2QAKq2EAQwgCGEAQwgCGEAQwgCEO1A8ARPywgAAAwACAAADAHkI,aO48sA==
    m=audio 0 RTP/AVP 97
    c=IN IP4 0.0.0.0
    a=rtpmap:97 mpeg4-generic/48000/2
    a=control:track2
    a=fmtp: 97 streamtype=3;profile-level-id=14;mode=AAC-hbr;config=1190;sizeLength=13
    
    opened by BenLocal 4
  • panic inside RingBuffer.Push() called from ServerSessioin.WritePacketRTCP()

    panic inside RingBuffer.Push() called from ServerSessioin.WritePacketRTCP()

    backtrace is as follows:

    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x86ae0e]
    goroutine 366 [running]:
    github.com/aler9/gortsplib/pkg/ringbuffer.(*RingBuffer).Push(0x0, {0x947d20, 0xc0002f0240})
           /home/uchida/go/pkg/mod/github.com/aler9/[email protected]/aler9/gortsplib.(*ServerSession).WritePacketRTCP(0xc0000e6100, 0x1, {0xc000386060, 0x1c, 0x1c})
           /home/uchida/go/pkg/mod/github.com/aler9/[email protected]/serversession.go:1222 +0xc7
    
    bug 
    opened by nu774 4
  • split Tracks and Medias

    split Tracks and Medias

    Tracks and Medias are now two different entities.

    Fixes #146 One step closer to fixing https://github.com/aler9/rtsp-simple-server/issues/1103

    TODO:

    • [ ] media tests
    • [ ] RTP-Info
    • [ ] Fix RTCP sender and receiver reports in order to account for multiple tracks
    • [ ] dedicated callback per tracks?
    opened by aler9 1
  • Support multiple tracks inside single media

    Support multiple tracks inside single media

    I am working on a project for support WEBRTC based on rtsp-simple-server, and found below issue:

    when pass the webrtc SDP into newTrackFromMediaDescription , the track can not be created correctly. And I fix this by below change FYI.

    track.go

    @@ -85,7 +85,7 @@ func newTrackFromMediaDescription(md *psdp.MediaDescription) (Track, error) {
    -	if len(md.MediaName.Formats) == 1 {
    +	if len(md.MediaName.Formats) >= 1 {
    

    webrtc sdp for test:

    
    o=- 4158123474391860926 2 IN IP4 127.0.0.1
    s=-
    t=0 0
    a=group:BUNDLE audio video
    a=msid-semantic: WMS mediaStreamLocal
    m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126
    c=IN IP4 0.0.0.0
    a=rtcp:9 IN IP4 0.0.0.0
    a=ice-ufrag:0D6Y
    a=ice-pwd:V3YEqLGAJJhUDUa13C/pKbWe
    a=ice-options:trickle renomination
    a=fingerprint:sha-256 5E:B5:97:8B:B4:D8:AE:2B:89:F6:82:44:47:69:77:83:05:29:C5:C8:EE:67:50:C3:77:6B:A7:BA:10:E3:08:B8
    a=setup:actpass
    a=mid:audio
    a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
    a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
    a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
    a=sendonly
    a=rtcp-mux
    a=rtpmap:111 opus/48000/2
    a=rtcp-fb:111 transport-cc
    a=fmtp:111 minptime=10;useinbandfec=1
    a=rtpmap:103 ISAC/16000
    a=rtpmap:104 ISAC/32000
    a=rtpmap:9 G722/8000
    a=rtpmap:102 ILBC/8000
    a=rtpmap:0 PCMU/8000
    a=rtpmap:8 PCMA/8000
    a=rtpmap:106 CN/32000
    a=rtpmap:105 CN/16000
    a=rtpmap:13 CN/8000
    a=rtpmap:110 telephone-event/48000
    a=rtpmap:112 telephone-event/32000
    a=rtpmap:113 telephone-event/16000
    a=rtpmap:126 telephone-event/8000
    a=ssrc:3754810229 cname:CvU1TYqkVsjj5XOt
    a=ssrc:3754810229 msid:mediaStreamLocal 101
    a=ssrc:3754810229 mslabel:mediaStreamLocal
    a=ssrc:3754810229 label:101
    m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 125
    c=IN IP4 0.0.0.0
    a=rtcp:9 IN IP4 0.0.0.0
    a=ice-ufrag:0D6Y
    a=ice-pwd:V3YEqLGAJJhUDUa13C/pKbWe
    a=ice-options:trickle renomination
    a=fingerprint:sha-256 5E:B5:97:8B:B4:D8:AE:2B:89:F6:82:44:47:69:77:83:05:29:C5:C8:EE:67:50:C3:77:6B:A7:BA:10:E3:08:B8
    a=setup:actpass
    a=mid:video
    a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
    a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
    a=extmap:13 urn:3gpp:video-orientation
    a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
    a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
    a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
    a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
    a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
    a=sendonly
    a=rtcp-mux
    a=rtcp-rsize
    a=rtpmap:96 VP8/90000
    a=rtcp-fb:96 goog-remb
    a=rtcp-fb:96 transport-cc
    a=rtcp-fb:96 ccm fir
    a=rtcp-fb:96 nack
    a=rtcp-fb:96 nack pli
    a=rtpmap:97 rtx/90000
    a=fmtp:97 apt=96
    a=rtpmap:98 VP9/90000
    a=rtcp-fb:98 goog-remb
    a=rtcp-fb:98 transport-cc
    a=rtcp-fb:98 ccm fir
    a=rtcp-fb:98 nack
    a=rtcp-fb:98 nack pli
    a=rtpmap:99 rtx/90000
    a=fmtp:99 apt=98
    a=rtpmap:100 H264/90000
    a=rtcp-fb:100 goog-remb
    a=rtcp-fb:100 transport-cc
    a=rtcp-fb:100 ccm fir
    a=rtcp-fb:100 nack
    a=rtcp-fb:100 nack pli
    a=fmtp:100 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
    a=rtpmap:101 rtx/90000
    a=fmtp:101 apt=100
    a=rtpmap:127 red/90000
    a=rtpmap:124 rtx/90000
    a=fmtp:124 apt=127
    a=rtpmap:125 ulpfec/90000
    a=ssrc-group:FID 2712436124 1733091158
    a=ssrc:2712436124 cname:CvU1TYqkVsjj5XOt
    a=ssrc:2712436124 msid:mediaStreamLocal 100
    a=ssrc:2712436124 mslabel:mediaStreamLocal
    a=ssrc:2712436124 label:100
    a=ssrc:1733091158 cname:CvU1TYqkVsjj5XOt
    a=ssrc:1733091158 msid:mediaStreamLocal 100
    a=ssrc:1733091158 mslabel:mediaStreamLocal
    a=ssrc:1733091158 label:100
    
    
    
    
    bug 
    opened by sureone 1
  • transport header does not contain interleaved IDs

    transport header does not contain interleaved IDs

    The camera I am trying to connect to apparently does not follow the standard very well and I am not able to use it with rtsp-simple-server. FFMPEG and VLC are able to connect to this camera.

    Is it possible to relax gortsplib's checking of the stream for correctness?

    Log:

    2022/10/26 03:59:16 INF rtsp-simple-server v0.20.0
    2022/10/26 03:59:16 INF [path g300a] [rtsp source] started
    2022/10/26 03:59:16 INF [RTSP] listener opened on :8554 (TCP), :8000 (UDP/RTP), :8001 (UDP/RTCP)
    2022/10/26 03:59:16 INF [path g300a] [rtsp source] ready: 1 track (H264)
    2022/10/26 03:59:19 INF [path g300a] [rtsp source] ERR: transport header does not contain interleaved IDs
    2022/10/26 03:59:25 INF [path g300a] [rtsp source] ready: 1 track (H264)
    2022/10/26 03:59:28 INF [path g300a] [rtsp source] ERR: transport header does not contain interleaved IDs
    2022/10/26 03:59:33 INF [path g300a] [rtsp source] ready: 1 track (H264)
    2022/10/26 03:59:37 INF [path g300a] [rtsp source] ERR: transport header does not contain interleaved IDs
    2022/10/26 03:59:42 INF [path g300a] [rtsp source] ready: 1 track (H264)
    2022/10/26 03:59:45 INF [path g300a] [rtsp source] ERR: transport header does not contain interleaved IDs
    2022/10/26 03:59:51 INF [path g300a] [rtsp source] ready: 1 track (H264)
    2022/10/26 03:59:54 INF [path g300a] [rtsp source] ERR: transport header does not contain interleaved IDs
    2022/10/26 03:59:59 INF [path g300a] [rtsp source] ready: 1 track (H264)
    
    

    Here is a the log at debug level:

    2022/10/26 04:03:12 INF rtsp-simple-server v0.20.0
    2022/10/26 04:03:12 DEB [path g300a] created
    2022/10/26 04:03:12 DEB path manager created
    2022/10/26 04:03:12 INF [path g300a] [rtsp source] started
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] connecting
    2022/10/26 04:03:12 INF [RTSP] listener opened on :8554 (TCP), :8000 (UDP/RTP), :8001 (UDP/RTCP)
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] c->s OPTIONS rtsp://192.168.1.5/sid=111 RTSP/1.0
    CSeq: 1
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 1
    Date: 27 Apr 2000 05:35:39 GMT
    Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
    
    
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] c->s DESCRIBE rtsp://192.168.1.5/sid=111 RTSP/1.0
    Accept: application/sdp
    CSeq: 2
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 2
    Content-Base: rtsp://192.168.1.5/sid=111
    Content-Length: 326
    Content-Type: application/sdp
    Date: 27 Apr 2000 05:35:39 GMT
    
    v=0
    o=- 0 0 IN IP4 192.168.1.5
    s=IR stream
    i=Live infrared
    t=now-
    c=IN IP4 192.168.1.5
    m=video 0 RTP/AVP 111
    a=control:rtsp://192.168.1.5/sid=111&overlay=on
    a=framerate:30
    a=rtpmap:111 H264/90000
    a=framesize:111 640-480
    a=fmtp:111 profile-level-id=42001E;packetization-mode=1;sprop-parameter-sets=Z0IAHqtAUB7I,aM4xEg==
    
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] c->s SETUP rtsp://192.168.1.5/sid=111&overlay=on RTSP/1.0
    CSeq: 3
    Transport: RTP/AVP;unicast;client_port=46876-46877;mode=play
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 3
    Session: D0F90306
    Transport: RTP/AVP;unicast;client_port=46876-46877;server_port=46876-46877
    
    
    2022/10/26 04:03:12 INF [path g300a] [rtsp source] ready: 1 track (H264)
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] c->s PLAY rtsp://192.168.1.5/sid=111 RTSP/1.0
    CSeq: 4
    Range: npt=0-
    Session: D0F90306
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:12 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 4
    Range: npt=0.000000-
    Session: D0F90306
    
    
    2022/10/26 04:03:15 DEB [path g300a] [rtsp source] c->s TEARDOWN rtsp://192.168.1.5/sid=111 RTSP/1.0
    CSeq: 5
    Session: D0F90306
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:15 DEB [path g300a] [rtsp source] c->s OPTIONS rtsp://192.168.1.5/sid=111 RTSP/1.0
    CSeq: 1
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:15 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 1
    Date: 27 Apr 2000 05:35:42 GMT
    Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
    
    
    2022/10/26 04:03:15 DEB [path g300a] [rtsp source] c->s DESCRIBE rtsp://192.168.1.5/sid=111 RTSP/1.0
    Accept: application/sdp
    CSeq: 2
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:15 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 2
    Content-Base: rtsp://192.168.1.5/sid=111
    Content-Length: 326
    Content-Type: application/sdp
    Date: 27 Apr 2000 05:35:42 GMT
    
    v=0
    o=- 0 0 IN IP4 192.168.1.5
    s=IR stream
    i=Live infrared
    t=now-
    c=IN IP4 192.168.1.5
    m=video 0 RTP/AVP 111
    a=control:rtsp://192.168.1.5/sid=111&overlay=on
    a=framerate:30
    a=rtpmap:111 H264/90000
    a=framesize:111 640-480
    a=fmtp:111 profile-level-id=42001E;packetization-mode=1;sprop-parameter-sets=Z0IAHqtAUB7I,aM4xEg==
    
    2022/10/26 04:03:15 DEB [path g300a] [rtsp source] c->s SETUP rtsp://192.168.1.5/sid=111&overlay=on RTSP/1.0
    CSeq: 3
    Transport: RTP/AVP/TCP;unicast;interleaved=0-1;mode=play
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:16 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 3
    Session: D0F90306
    Transport: RTP/AVP;unicast;client_port=0-0;server_port=0-1
    
    
    2022/10/26 04:03:16 INF [path g300a] [rtsp source] ERR: transport header does not contain interleaved IDs
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] connecting
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] c->s OPTIONS rtsp://192.168.1.5/sid=111 RTSP/1.0
    CSeq: 1
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 1
    Date: 27 Apr 2000 05:35:47 GMT
    Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
    
    
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] c->s DESCRIBE rtsp://192.168.1.5/sid=111 RTSP/1.0
    Accept: application/sdp
    CSeq: 2
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 2
    Content-Base: rtsp://192.168.1.5/sid=111
    Content-Length: 326
    Content-Type: application/sdp
    Date: 27 Apr 2000 05:35:47 GMT
    
    v=0
    o=- 0 0 IN IP4 192.168.1.5
    s=IR stream
    i=Live infrared
    t=now-
    c=IN IP4 192.168.1.5
    m=video 0 RTP/AVP 111
    a=control:rtsp://192.168.1.5/sid=111&overlay=on
    a=framerate:30
    a=rtpmap:111 H264/90000
    a=framesize:111 640-480
    a=fmtp:111 profile-level-id=42001E;packetization-mode=1;sprop-parameter-sets=Z0IAHqtAUB7I,aM4xEg==
    
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] c->s SETUP rtsp://192.168.1.5/sid=111&overlay=on RTSP/1.0
    CSeq: 3
    Transport: RTP/AVP;unicast;client_port=10096-10097;mode=play
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 3
    Session: D0F90306
    Transport: RTP/AVP;unicast;client_port=10096-10097;server_port=10096-10097
    
    
    2022/10/26 04:03:21 INF [path g300a] [rtsp source] ready: 1 track (H264)
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] c->s PLAY rtsp://192.168.1.5/sid=111 RTSP/1.0
    CSeq: 4
    Range: npt=0-
    Session: D0F90306
    User-Agent: gortsplib
    
    
    2022/10/26 04:03:21 DEB [path g300a] [rtsp source] s->c RTSP/1.0 200 OK
    CSeq: 4
    Range: npt=0.000000-
    Session: D0F90306
    
    
    bug 
    opened by creategui 3
  • RTPListener doesn't work

    RTPListener doesn't work

    Thank you for your lib before writing issue.

    i tried to make sample in window with this library and it make a successful.

    so i saw your reference main.go and i setup ffmpeg in window in order to use h264decoder.go

    this link is ffmpeg library (it's good with window) ffmpeg-master-latest-win64-gpl.zip

    and i change the cgo flag like this

    // #cgo CFLAGS: -I D:/Camera/ffmpeg-master-latest-win64-gpl-shared/include // #cgo LDFLAGS: -Ld D:/Camera/ffmpeg-master-latest-win64-gpl-shared/lib/libavcodec.dll.a // #cgo LDFLAGS: -Ld D:/Camera/ffmpeg-master-latest-win64-gpl-shared/lib/libavutil.dll.a // #cgo LDFLAGS: -Ld D:/Camera/ffmpeg-master-latest-win64-gpl-shared/lib/libswscale.dll.a // #include <libavcodec/avcodec.h> // #include <libavutil/imgutils.h> // #include <libswscale/swscale.h> import "C"

    until that, it is my development environment. and from down here, i think it is issue.

    with your sample, it should have worked normally. but, in my environment, RTCP callback function work well but RTP callback function doesn't called. :(

    so i debuged with goland, i found the expected part. in client.go with 713 - 716 Line, it call function start (clientudpl.go) [client.go 713-716] for _, ct := range c.tracks { ct.udpRTPListener.start(true) ct.udpRTCPListener.start(true) } [clientudpl.go start] func (u *clientUDPListener) start(forPlay bool) { u.running = true u.pc.SetReadDeadline(time.Time{}) u.readerDone = make(chan struct{}) go u.runReader(forPlay) }

    in this start function, it called goroutine u.runReader. I don't know the internal movement of goroutine, but it wasn't called. (only udpRTPListener.start)

    so i set delay in client.go, it works well [client.go 713-716] for _, ct := range c.tracks { ct.udpRTPListener.start(true) ++ time.Sleep(time.Second * time.Duration(3)) // at least 3 second. not worked in 2 second ct.udpRTCPListener.start(true) }

    The exact cause was not found, but symptoms were found so i think it seemed to be reported.

    i attach my sample code and i hope it helps to resolve that Symptom.

    thank you.

    package main

    import ( "fmt" "github.com/aler9/gortsplib" "github.com/aler9/gortsplib/pkg/base" "github.com/aler9/gortsplib/pkg/url" "image" "image/jpeg" "os" "runtime" "strconv" "time" )

    func saveToFile(img image.Image) error { fname := "D://"+strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10) + ".jpg" //

    f, err := os.Create(fname)
    if err != nil {
    	fmt.Println("os.Create fname fail ", err)
    	os.Exit(1)
    }
    defer f.Close()
    
    fmt.Println("saving", fname)
    
    // convert to jpeg
    return jpeg.Encode(f, img, &jpeg.Options{
    	Quality: 60,
    })
    

    }

    func main() { runtime.GOMAXPROCS(8) c := gortsplib.Client{}

    // parse URL
    u, err := url.Parse("rtsp://myid:[email protected]:554/live4.sdp")
    if err != nil {
    	fmt.Println("URL Parse ", err)
    	os.Exit(1)
    }
    
    // connect to the server
    err = c.Start(u.Scheme, u.Host)
    if err != nil {
    	fmt.Println("Start ", err)
    	os.Exit(1)
    }
    defer c.Close()
    
    
    // find published tracks
    fmt.Println("Describe")
    tracks, baseURL, _, err := c.Describe(u)
    if err != nil {
    	fmt.Println("Describe ", err)
    	os.Exit(1)
    }
    
    // find the H264 track
    h264TrackID, h264track := func() (int, *gortsplib.TrackH264) {
    	for i, track := range tracks {
    		if h264track, ok := track.(*gortsplib.TrackH264); ok {
    			return i, h264track
    		}
    	}
    	return -1, nil
    }()
    
    if h264TrackID < 0 {
    	fmt.Println("H264 track not found")
    	os.Exit(1)
    }
    
    // setup H264->raw frames decoder
    h264dec, err := newH264Decoder()
    if err != nil {
    	fmt.Println("newH264Decoder ", err)
    	os.Exit(1)
    }
    defer h264dec.close()
    
    // if present, send SPS and PPS from the SDP to the decoder
    sps := h264track.SafeSPS()
    if sps != nil {
    	h264dec.decode(sps)
    }
    pps := h264track.SafePPS()
    if pps != nil {
    	h264dec.decode(pps)
    }
    
    
    // Callback Function
    saveCount := 0
    // Callback Video Udp Data
    c.OnPacketRTP = func(ctx *gortsplib.ClientOnPacketRTPCtx) {
    	fmt.Printf("[RTP]\n%v, %v, %v\n", ctx.TrackID, ctx.H264PTS, ctx.PTSEqualsDTS) //ctx.Packet, ctx.H264NALUs, is frame Data
    	if ctx.TrackID != h264TrackID {
    		return
    	}
    
    	if ctx.H264NALUs == nil {
    		fmt.Println("H264NALUs == nil")
    		return
    	}
    
    	for _, nalu := range ctx.H264NALUs {
    		// convert H264 NALUs to RGBA frames
    		img, err := h264dec.decode(nalu)
    		if err != nil {
    			fmt.Println("h264dec.decode ", err)
    			os.Exit(1)
    		}
    
    		// wait for a frame
    		if img == nil {
    			continue
    		}
    		// ==== Save Image Start ====
    		err = saveToFile(img)
    		if err != nil {
    			fmt.Println("saveToFile ", err)
    			os.Exit(1)
    		}
    		saveCount++
    		if saveCount == 5 {
    			fmt.Println("saved 5 images Succ")
    			os.Exit(1)
    		}
    		// ==== Save Image End ====
    
    		//fmt.Printf("%v", img) // img data
    		fmt.Printf("decoded frame with size %v", img.Bounds().Max)
    
    
    	}
    }
    // Callback Rtp Check Data
    c.OnPacketRTCP = func(ctx *gortsplib.ClientOnPacketRTCPCtx) {
    	fmt.Printf("[RTCP]\n%v, %v", ctx.TrackID, ctx.Packet)
    }
    
    // When Response msg from rtsp
    c.OnResponse = func(resp *base.Response) {
    	fmt.Printf("%v, %v, %v, %v\n", resp.StatusMessage, resp.StatusCode, resp.Header, resp.Body)
    }
    
    // When Send smg from rtsp
    c.OnRequest = func(req *base.Request) {
    	fmt.Printf("%v, %v, %v, %v\n", req.Method, req.URL, req.Header, req.Body)
    }
    
    // i wanna set Setup(false, t, baseURL, 0, 0) , so i didn't use SetupAndPlay Func
    fmt.Println("Setup")
    for _, t := range tracks {
    	_, err := c.Setup(true, t, baseURL, 0, 0) // i don't know why i can't set (forPlay == false) in Setup Function.
    	if err != nil {
    		fmt.Println("hihi", err)
    	}
    }
    
    fmt.Println("Play")
    _, err = c.Play(nil)
    
    // wait until a fatal error
    panic(c.Wait())
    

    }

    //[decoder.go] // https://github.com/aler9/gortsplib/blob/main/examples/client-read-h264/h264decoder.go package main

    import ( "fmt" "image" "unsafe" ) // #cgo CFLAGS: -I D:/Camera/ffmpeg-master-latest-win64-gpl-shared/include // #cgo LDFLAGS: -Ld D:/Camera/ffmpeg-master-latest-win64-gpl-shared/lib/libavcodec.dll.a // #cgo LDFLAGS: -Ld D:/Camera/ffmpeg-master-latest-win64-gpl-shared/lib/libavutil.dll.a // #cgo LDFLAGS: -Ld D:/Camera/ffmpeg-master-latest-win64-gpl-shared/lib/libswscale.dll.a // #include <libavcodec/avcodec.h> // #include <libavutil/imgutils.h> // #include <libswscale/swscale.h> import "C"

    func frameData(frame *C.AVFrame) **C.uint8_t { return (**C.uint8_t)(unsafe.Pointer(&frame.data[0])) }

    func frameLineSize(frame *C.AVFrame) *C.int { return (*C.int)(unsafe.Pointer(&frame.linesize[0])) }

    // h264Decoder is a wrapper around ffmpeg's H264 decoder. type h264Decoder struct { codecCtx *C.AVCodecContext srcFrame *C.AVFrame swsCtx *C.struct_SwsContext dstFrame *C.AVFrame dstFramePtr []uint8 }

    // newH264Decoder allocates a new h264Decoder. func newH264Decoder() (*h264Decoder, error) { codec := C.avcodec_find_decoder(C.AV_CODEC_ID_H264) if codec == nil { return nil, fmt.Errorf("avcodec_find_decoder() failed") }

    codecCtx := C.avcodec_alloc_context3(codec)
    if codecCtx == nil {
    	return nil, fmt.Errorf("avcodec_alloc_context3() failed")
    }
    
    res := C.avcodec_open2(codecCtx, codec, nil)
    if res < 0 {
    	C.avcodec_close(codecCtx)
    	return nil, fmt.Errorf("avcodec_open2() failed")
    }
    
    srcFrame := C.av_frame_alloc()
    if srcFrame == nil {
    	C.avcodec_close(codecCtx)
    	return nil, fmt.Errorf("av_frame_alloc() failed")
    }
    
    return &h264Decoder{
    	codecCtx: codecCtx,
    	srcFrame: srcFrame,
    }, nil
    

    }

    // close closes the decoder. func (d *h264Decoder) close() { if d.dstFrame != nil { C.av_frame_free(&d.dstFrame) }

    if d.swsCtx != nil {
    	C.sws_freeContext(d.swsCtx)
    }
    
    C.av_frame_free(&d.srcFrame)
    C.avcodec_close(d.codecCtx)
    

    }

    func (d *h264Decoder) decode(nalu []byte) (image.Image, error) { nalu = append([]uint8{0x00, 0x00, 0x00, 0x01}, []uint8(nalu)...)

    // send frame to decoder
    var avPacket C.AVPacket
    avPacket.data = (*C.uint8_t)(C.CBytes(nalu))
    defer C.free(unsafe.Pointer(avPacket.data))
    avPacket.size = C.int(len(nalu))
    res := C.avcodec_send_packet(d.codecCtx, &avPacket)
    if res < 0 {
    	return nil, nil
    }
    
    // receive frame if available
    res = C.avcodec_receive_frame(d.codecCtx, d.srcFrame)
    if res < 0 {
    	return nil, nil
    }
    
    // if frame size has changed, allocate needed objects
    if d.dstFrame == nil || d.dstFrame.width != d.srcFrame.width || d.dstFrame.height != d.srcFrame.height {
    	if d.dstFrame != nil {
    		C.av_frame_free(&d.dstFrame)
    	}
    
    	if d.swsCtx != nil {
    		C.sws_freeContext(d.swsCtx)
    	}
    
    	d.dstFrame = C.av_frame_alloc()
    	d.dstFrame.format = C.AV_PIX_FMT_RGBA
    	d.dstFrame.width = d.srcFrame.width
    	d.dstFrame.height = d.srcFrame.height
    	d.dstFrame.color_range = C.AVCOL_RANGE_JPEG
    	res = C.av_frame_get_buffer(d.dstFrame, 1)
    	if res < 0 {
    		return nil, fmt.Errorf("av_frame_get_buffer() err")
    	}
    
    	d.swsCtx = C.sws_getContext(d.srcFrame.width, d.srcFrame.height, C.AV_PIX_FMT_YUV420P,
    		d.dstFrame.width, d.dstFrame.height, (int32)(d.dstFrame.format), C.SWS_BILINEAR, nil, nil, nil)
    	if d.swsCtx == nil {
    		return nil, fmt.Errorf("sws_getContext() err")
    	}
    
    	dstFrameSize := C.av_image_get_buffer_size((int32)(d.dstFrame.format), d.dstFrame.width, d.dstFrame.height, 1)
    	d.dstFramePtr = (*[1 << 30]uint8)(unsafe.Pointer(d.dstFrame.data[0]))[:dstFrameSize:dstFrameSize]
    }
    
    // convert frame from YUV420 to RGB
    res = C.sws_scale(d.swsCtx, frameData(d.srcFrame), frameLineSize(d.srcFrame),
    	0, d.srcFrame.height, frameData(d.dstFrame), frameLineSize(d.dstFrame))
    if res < 0 {
    	return nil, fmt.Errorf("sws_scale() err")
    }
    
    // embed frame into an image.Image
    return &image.RGBA{
    	Pix:    d.dstFramePtr,
    	Stride: 4 * (int)(d.dstFrame.width),
    	Rect: image.Rectangle{
    		Max: image.Point{(int)(d.dstFrame.width), (int)(d.dstFrame.height)},
    	},
    }, nil
    

    }

    question 
    opened by cwcsh95 2
  • Unchecked errors

    Unchecked errors

    I suggest enabling errcheck in golangci-lint and adding //nolint:errcheck where errors are intentionally ignored.

    golangci-lint output
    client.go:338:10: Error return value of `c.Close` is not checked (errcheck)
    client.go:344:10: Error return value of `c.Close` is not checked (errcheck)
    client.go:376:10: Error return value of `c.Close` is not checked (errcheck)
    client.go:476:25: Error return value of `c.WritePacketRTCP` is not checked (errcheck)			                 
    client.go:486:25: Error return value of `c.WritePacketRTCP` is not checked (errcheck)	                 
    client.go:597:7: Error return value of `c.do` is not checked (errcheck)
    client.go:602:15: Error return value of `c.conn.Close` is not checked (errcheck)
    client.go:606:15: Error return value of `c.conn.Close` is not checked (errcheck)
    client.go:736:24: Error return value of `c.conn.SetReadDeadline` is not checked (errcheck)
    client.go:810:25: Error return value of `c.conn.SetReadDeadline` is not checked (errcheck)
    client.go:886:16: Error return value of `c.conn.Close` is not checked (errcheck)
    client.go:934:26: Error return value of `c.conn.SetWriteDeadline` is not checked (errcheck)
    client.go:944:25: Error return value of `c.conn.SetReadDeadline` is not checked (errcheck)
    client.go:1600:28: Error return value of `cct.udpRTPListener.write` is not checked (errcheck)
    client.go:1603:29: Error return value of `cct.udpRTCPListener.write` is not checked (errcheck)
    client.go:1818:43: Error return value of `.write` is not checked (errcheck)                 
    client.go:1820:44: Error return value of `.write` is not checked (errcheck)                         
    client.go:1837:28: Error return value of `c.conn.SetWriteDeadline` is not checked (errcheck)          
    client.go:1838:17: Error return value of `c.conn.Write` is not checked (errcheck)
    client.go:1844:28: Error return value of `c.conn.SetWriteDeadline` is not checked (errcheck)
    client.go:1845:17: Error return value of `c.conn.Write` is not checked (errcheck)
    client_publish_test.go:30:17: Error return value of `l.Close` is not checked (errcheck)
    client_publish_test.go:51:21: Error return value of `conn.Close` is not checked (errcheck)
    client_publish_test.go:98:20: Error return value of `l1.Close` is not checked (errcheck)
    client_publish_test.go:102:20: Error return value of `l2.Close` is not checked (errcheck)
    client_publish_test.go:158:16: Error return value of `l2.WriteTo` is not checked (errcheck)
    client_publish_test.go:216:11: Error return value of `c.Wait` is not checked (errcheck)
    client_publish_test.go:243:17: Error return value of `l.Close` is not checked (errcheck)
    client_publish_test.go:264:21: Error return value of `conn.Close` is not checked (errcheck)
    client_publish_test.go:404:17: Error return value of `l.Close` is not checked (errcheck)
    client_publish_test.go:413:21: Error return value of `conn.Close` is not checked (errcheck)
    client_publish_test.go:890:17: Error return value of `l1.Close` is not checked (errcheck)
    client_publish_test.go:894:17: Error return value of `l2.Close` is not checked (errcheck)
    client_read_test.go:281:20: Error return value of `l1.Close` is not checked (errcheck)
    client_read_test.go:285:20: Error return value of `l2.Close` is not checked (errcheck)
    client_read_test.go:355:16: Error return value of `l1.WriteTo` is not checked (errcheck)
    client_read_test.go:362:16: Error return value of `l1.WriteTo` is not checked (errcheck)
    client_read_test.go:910:20: Error return value of `l1a.Close` is not checked (errcheck)
    client_read_test.go:914:20: Error return value of `l1b.Close` is not checked (errcheck)
    client_read_test.go:959:16: Error return value of `l1a.WriteTo` is not checked (errcheck)
    client_read_test.go:995:22: Error return value of `c.WritePacketRTCP` is not checked (errcheck)
    client_read_test.go:1624:13: Error return value of `l1.WriteTo` is not checked (errcheck)
    client_read_test.go:1678:17: Error return value of `conn.Write` is not checked (errcheck)
    clientudpl.go:17:11: Error return value of `rand.Read` is not checked (errcheck)
    clientudpl.go:124:12: Error return value of `l.pc.Close` is not checked (errcheck)
    clientudpl.go:143:22: Error return value of `l.pc.SetReadDeadline` is not checked (errcheck)
    clientudpl.go:149:22: Error return value of `l.pc.SetReadDeadline` is not checked (errcheck)
    clientudpl.go:194:23: Error return value of `l.pc.SetWriteDeadline` is not checked (errcheck)
    server.go:356:17: Error return value of `nconn.Close` is not checked (errcheck)
    server.go:382:13: Error return value of `sc.Close` is not checked (errcheck)
    
    enhancement 
    opened by Curid 0
Owner
Alessandro Ros
Software and robotics engineer, i deal with anything that can be modeled and controlled. MSc @ PoliMi
Alessandro Ros
rtsp to webrtc proxy with websocket signaling, currently limited to single h264 stream per endpoint

rtp-to-webrtc rtp-to-webrtc demonstrates how to consume a RTP stream video UDP, and then send to a WebRTC client. With this example we have pre-made G

Game On 5 Aug 6, 2022
Parser and generator of M3U8-playlists for Apple HLS. Library for Go language. :cinema:

M3U8 This is the most complete opensource library for parsing and generating of M3U8 playlists used in HTTP Live Streaming (Apple HLS) for internet vi

Alexander I.Grafov 978 Nov 17, 2022
Personal video streaming server.

tube This is a Golang project to build a self hosted "tube"-style video player for watching your own video collection over HTTP or hosting your own ch

davy wybiral 236 Nov 25, 2022
live video streaming server in golang

中文 Simple and efficient live broadcast server: Very simple to install and use; Pure Golang, high performance, and cross-platform; Supports commonly us

浩麟 8.4k Nov 22, 2022
live streaming server in golang

中文 Simple and efficient live broadcast server: Very simple to install and use; Pure Golang, high performance, and cross-platform; Supports commonly us

BigSillyBear 27 Nov 10, 2022
golang library to read and write various subtitle formats

libgosubs Golang library to read and write subtitles in the following formats Advanced SubStation Alpha v4 SRT TTML v1.0 - This is based on the spec p

Wargarblgarbl 20 Sep 27, 2022
A simple library to extract video and audio frames from media containers (based on libav).

Reisen A simple library to extract video and audio frames from media containers (based on libav, i.e. ffmpeg). Dependencies The library requires libav

NightGhost 73 Nov 15, 2022
golang library for mux and demux file containers such as mpeg-ts,mpeg-ps,flv

gomedia mpeg-ts,mpeg-ps,flv muxer/demuxer mpeg-ts muxer mpeg-ts demuxer mpeg-ps muxer mpeg-ps demuxer flv muxer flv demuxer mpeg-ps will be done in th

yaping 105 Nov 20, 2022
lmmp3 is a little golang library that download a video from youtube, and convert it to a mp3 file using ffmpeg

lmmp3 lmmp3 is a function that download a video from youtube, and convert it to a mp3 file using ffmpeg You need to have installed ffmpeg in your syst

pai 8 Aug 12, 2022
Go4vl is Go library for working with the Video for Linux API (V4L2) natively, without any C bindings.

go4vl A Go library for working with the Video for Linux user API (V4L2). Gov4l hides all the complexities of working with V4L2 and exposes idiomatic G

Vladimir Vivien 128 Nov 17, 2022
Package implement reading and writing popular playlist formats: PLS, ASX, M3U, XSPF and others.

go-playlist ⚠️ WARNING The API is not stable yet and can change. Package playlist implement reading and writing popular playlist formats: PLS, ASX, M3

Roman Kulish 0 Oct 14, 2021
ffcommander - An easy frontend to FFmpeg and Imagemagick to automatically process video and manipulate subtitles.

% FFCOMMANDER(1) ffcommander 2.39 % Mikael Hartzell (C) 2018 % 2021 Name ffcommander - An easy frontend to FFmpeg and Imagemagick to automatically pro

Mikael Hartzell 2 May 9, 2022
Parse and demux MPEG Transport Streams (.ts) natively in GO

This is a Golang library to natively parse and demux MPEG Transport Streams (ts) in GO. WARNING: this library is not yet production ready. Use at your

Quentin Renard 448 Nov 21, 2022
Parse and generate m3u8 playlists for Apple HTTP Live Streaming (HLS) in Golang (ported from gem https://github.com/sethdeckard/m3u8)

go-m3u8 Golang package for m3u8 (ported m3u8 gem https://github.com/sethdeckard/m3u8) go-m3u8 provides easy generation and parsing of m3u8 playlists d

Tan Quang Ngo 102 Nov 19, 2022
Go bindings for libVLC and high-level media player interface

Go bindings for libVLC 2.X/3.X/4.X and high-level media player interface. The package can be useful for adding multimedia capabilities to applications

Adrian-George Bostan 324 Nov 11, 2022
Plays videos using Prometheus and Grafana, e.g. Bad Apple.

prometheus_video_renderer Plays videos using Prometheus and Grafana, e.g. Bad Apple. Modes Currently 3 different modes are supported. Bitmap The bitma

Jacob Colvin 91 Nov 15, 2022
falco is VCL parser and linter optimized for Fastly.

falco falco is VCL parser and linter optimized for Fastly. Disclaimer This is a VCL parser, but dedicated to Fastly's VCL (version 2.x), so we don't c

Yoshiaki Sugimoto 62 Nov 14, 2022
Desktop application to download videos and playlists from youtube by simply copying its url.

tubemp3 Desktop application to download videos and playlists from youtube by simply copying its url. You just need to run tubemp3 and copy (CTRL + C)

Fernando Guevara Sanchez 13 Oct 25, 2022
Project to get Youtube video descriptions and search those videos as required

FamPayProject Project to get Youtube video descriptions and search those videos as required Prerequisities Postgres DB for persisting data Youtube Dat

null 0 Nov 5, 2021